claude-smart 0.2.47 → 0.2.49
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 +6 -2
- package/bin/claude-smart.js +289 -56
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- 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 +60 -2
- package/plugin/opencode/server.mts +64 -2
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +58 -6
- package/plugin/scripts/backend-service.sh +15 -10
- package/plugin/scripts/codex-hook.js +16 -4
- package/plugin/scripts/dashboard-service.sh +1 -1
- package/plugin/scripts/ensure-plugin-root.sh +106 -8
- package/plugin/scripts/opencode-claude-compat.js +8 -1
- package/plugin/scripts/smart-install.sh +101 -20
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +79 -29
- package/plugin/src/claude_smart/env_config.py +53 -11
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +8 -5
- package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +40 -6
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +15 -11
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +18 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +24 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +25 -8
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +148 -3277
- 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/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -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 +72 -1817
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +56 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +183 -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/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 +320 -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 +3 -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/durable_learning/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +24 -41
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +335 -113
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +362 -81
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -490
- 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/profile/service.py +44 -22
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +31 -4
- 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 +196 -353
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +0 -1513
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +379 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +17 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1281
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
- 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 +216 -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/_agent.py +22 -10
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +25 -12
- 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 +308 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +920 -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 -8
- 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/_commit_scope.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +219 -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/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +95 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +60 -80
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +0 -148
package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Interaction + profile search methods for SQLite storage.
|
|
2
|
+
|
|
3
|
+
Extracted verbatim from ``_profiles.py`` (the Search bucket). Profile CRUD lives
|
|
4
|
+
in ``profiles._profile_store``; interaction CRUD in ``profiles._interaction_store``.
|
|
5
|
+
The shared ``_build_tags_sql`` helper stays module-level in ``_profiles`` (it is
|
|
6
|
+
used by both this bucket and ProfileStore) and is imported here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from reflexio.models.api_schema.retriever_schema import (
|
|
13
|
+
SearchInteractionRequest,
|
|
14
|
+
SearchUserProfileRequest,
|
|
15
|
+
)
|
|
16
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
17
|
+
Interaction,
|
|
18
|
+
Status,
|
|
19
|
+
UserProfile,
|
|
20
|
+
)
|
|
21
|
+
from reflexio.models.config_schema import SearchMode
|
|
22
|
+
|
|
23
|
+
from .._base import (
|
|
24
|
+
SQLiteStorageBase,
|
|
25
|
+
_build_status_sql,
|
|
26
|
+
_effective_search_mode,
|
|
27
|
+
_epoch_now,
|
|
28
|
+
_row_to_interaction,
|
|
29
|
+
_row_to_profile,
|
|
30
|
+
_sanitize_fts_query,
|
|
31
|
+
_true_rrf_merge,
|
|
32
|
+
_vector_rank_rows,
|
|
33
|
+
)
|
|
34
|
+
from .._profiles import _build_tags_sql
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProfileSearchMixin:
|
|
40
|
+
"""Mixin providing profile + interaction search."""
|
|
41
|
+
|
|
42
|
+
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
43
|
+
_fetchall: Any
|
|
44
|
+
|
|
45
|
+
# ------------------------------------------------------------------
|
|
46
|
+
# Search — Interactions & Profiles
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
@SQLiteStorageBase.handle_exceptions
|
|
50
|
+
def search_interaction(
|
|
51
|
+
self,
|
|
52
|
+
search_interaction_request: SearchInteractionRequest,
|
|
53
|
+
query_embedding: list[float] | None = None,
|
|
54
|
+
) -> list[Interaction]:
|
|
55
|
+
req = search_interaction_request
|
|
56
|
+
has_query = bool(req.query)
|
|
57
|
+
match_count = req.most_recent_k or 10
|
|
58
|
+
mode = _effective_search_mode(req.search_mode, query_embedding, req.query)
|
|
59
|
+
|
|
60
|
+
conditions: list[str] = ["i.user_id = ?"]
|
|
61
|
+
params: list[str | int | float] = [req.user_id]
|
|
62
|
+
|
|
63
|
+
if req.request_id:
|
|
64
|
+
conditions.append("i.request_id = ?")
|
|
65
|
+
params.append(req.request_id)
|
|
66
|
+
if req.start_time:
|
|
67
|
+
conditions.append("i.created_at >= ?")
|
|
68
|
+
params.append(req.start_time.timestamp())
|
|
69
|
+
if req.end_time:
|
|
70
|
+
conditions.append("i.created_at <= ?")
|
|
71
|
+
params.append(req.end_time.timestamp())
|
|
72
|
+
|
|
73
|
+
where_clause = " AND ".join(conditions)
|
|
74
|
+
overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
|
|
75
|
+
|
|
76
|
+
# Vector-only: rank by embedding similarity
|
|
77
|
+
if (
|
|
78
|
+
mode in (SearchMode.VECTOR, SearchMode.HYBRID)
|
|
79
|
+
and query_embedding
|
|
80
|
+
and not has_query
|
|
81
|
+
):
|
|
82
|
+
vector_limit = match_count * 10
|
|
83
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
84
|
+
WHERE {where_clause}
|
|
85
|
+
ORDER BY i.created_at DESC
|
|
86
|
+
LIMIT ?"""
|
|
87
|
+
rows = self._fetchall(sql, (*params, vector_limit))
|
|
88
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
89
|
+
elif has_query:
|
|
90
|
+
# FTS search (with optional HYBRID re-ranking)
|
|
91
|
+
fts_query = _sanitize_fts_query(req.query) # type: ignore[arg-type]
|
|
92
|
+
fts_conditions = ["interactions_fts MATCH ?", *conditions]
|
|
93
|
+
fts_where = " AND ".join(fts_conditions)
|
|
94
|
+
fts_params: list[str | int | float] = [fts_query, *params, overfetch]
|
|
95
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
96
|
+
JOIN interactions_fts f ON i.interaction_id = f.rowid
|
|
97
|
+
WHERE {fts_where}
|
|
98
|
+
ORDER BY bm25(interactions_fts, 1.0, 2.0)
|
|
99
|
+
LIMIT ?"""
|
|
100
|
+
fts_rows = self._fetchall(sql, tuple(fts_params))
|
|
101
|
+
|
|
102
|
+
if mode == SearchMode.HYBRID and query_embedding:
|
|
103
|
+
vec_limit = match_count * 10
|
|
104
|
+
vec_sql = f"""SELECT i.* FROM interactions i
|
|
105
|
+
WHERE {where_clause}
|
|
106
|
+
ORDER BY i.created_at DESC
|
|
107
|
+
LIMIT ?"""
|
|
108
|
+
vec_candidates = self._fetchall(vec_sql, (*params, vec_limit))
|
|
109
|
+
vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
|
|
110
|
+
rows = _true_rrf_merge(
|
|
111
|
+
fts_rows,
|
|
112
|
+
vec_rows,
|
|
113
|
+
"interaction_id",
|
|
114
|
+
match_count,
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
rows = fts_rows[:match_count]
|
|
118
|
+
else:
|
|
119
|
+
if req.most_recent_k:
|
|
120
|
+
# No query — just fetch most recent interactions by time
|
|
121
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
122
|
+
WHERE {where_clause}
|
|
123
|
+
ORDER BY i.created_at DESC
|
|
124
|
+
LIMIT ?"""
|
|
125
|
+
rows = self._fetchall(sql, (*params, req.most_recent_k))
|
|
126
|
+
return [_row_to_interaction(r) for r in reversed(rows)]
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
interactions = [_row_to_interaction(r) for r in rows]
|
|
130
|
+
if req.most_recent_k:
|
|
131
|
+
sorted_ints = sorted(interactions, key=lambda x: x.created_at, reverse=True)
|
|
132
|
+
return list(reversed(sorted_ints[: req.most_recent_k]))
|
|
133
|
+
return interactions
|
|
134
|
+
|
|
135
|
+
@SQLiteStorageBase.handle_exceptions
|
|
136
|
+
def search_user_profile( # noqa: C901
|
|
137
|
+
self,
|
|
138
|
+
search_user_profile_request: SearchUserProfileRequest,
|
|
139
|
+
status_filter: list[Status | None] | None = None,
|
|
140
|
+
query_embedding: list[float] | None = None,
|
|
141
|
+
) -> list[UserProfile]:
|
|
142
|
+
if status_filter is None:
|
|
143
|
+
status_filter = [None]
|
|
144
|
+
|
|
145
|
+
req = search_user_profile_request
|
|
146
|
+
match_count = req.top_k or 10
|
|
147
|
+
current_ts = _epoch_now()
|
|
148
|
+
has_query = bool(req.query)
|
|
149
|
+
mode = _effective_search_mode(req.search_mode, query_embedding, req.query)
|
|
150
|
+
has_embedding = query_embedding is not None
|
|
151
|
+
logger.info(
|
|
152
|
+
"Profile search: requested_mode=%s, effective_mode=%s, has_query=%s, has_embedding=%s, user_id=%s",
|
|
153
|
+
req.search_mode,
|
|
154
|
+
mode,
|
|
155
|
+
has_query,
|
|
156
|
+
has_embedding,
|
|
157
|
+
req.user_id,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
conditions: list[str] = ["p.expiration_timestamp >= ?"]
|
|
161
|
+
params: list[object] = [current_ts]
|
|
162
|
+
|
|
163
|
+
if req.user_id:
|
|
164
|
+
conditions.append("p.user_id = ?")
|
|
165
|
+
params.append(req.user_id)
|
|
166
|
+
if req.start_time:
|
|
167
|
+
conditions.append("p.last_modified_timestamp >= ?")
|
|
168
|
+
params.append(int(req.start_time.timestamp()))
|
|
169
|
+
if req.end_time:
|
|
170
|
+
conditions.append("p.last_modified_timestamp <= ?")
|
|
171
|
+
params.append(int(req.end_time.timestamp()))
|
|
172
|
+
if req.source:
|
|
173
|
+
conditions.append("LOWER(p.source) = LOWER(?)")
|
|
174
|
+
params.append(req.source)
|
|
175
|
+
if status_filter is not None:
|
|
176
|
+
frag, sparams = _build_status_sql(status_filter)
|
|
177
|
+
conditions.append(frag)
|
|
178
|
+
params.extend(sparams)
|
|
179
|
+
tag_frag, tag_params = _build_tags_sql("p", req.tags)
|
|
180
|
+
if tag_frag:
|
|
181
|
+
conditions.append(tag_frag)
|
|
182
|
+
params.extend(tag_params)
|
|
183
|
+
|
|
184
|
+
where_clause = " AND ".join(conditions)
|
|
185
|
+
overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
|
|
186
|
+
|
|
187
|
+
# Pure vector search: fetch all candidates, rank by cosine similarity
|
|
188
|
+
if mode == SearchMode.VECTOR and query_embedding:
|
|
189
|
+
if req.generated_from_request_id:
|
|
190
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
191
|
+
params.append(req.generated_from_request_id)
|
|
192
|
+
where_clause = " AND ".join(conditions)
|
|
193
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
194
|
+
WHERE {where_clause}
|
|
195
|
+
ORDER BY p.last_modified_timestamp DESC"""
|
|
196
|
+
rows = self._fetchall(sql, tuple(params))
|
|
197
|
+
logger.info(
|
|
198
|
+
"VECTOR search: %d candidates fetched, ranking by embedding", len(rows)
|
|
199
|
+
)
|
|
200
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
201
|
+
elif has_query:
|
|
202
|
+
fts_query = _sanitize_fts_query(req.query) # type: ignore[arg-type]
|
|
203
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
204
|
+
JOIN profiles_fts f ON p.profile_id = f.profile_id
|
|
205
|
+
WHERE profiles_fts MATCH ?
|
|
206
|
+
AND {where_clause}
|
|
207
|
+
ORDER BY bm25(profiles_fts, 0.0, 1.0)
|
|
208
|
+
LIMIT ?"""
|
|
209
|
+
params_list: list[object] = [fts_query, *params, overfetch]
|
|
210
|
+
fts_rows = self._fetchall(sql, tuple(params_list))
|
|
211
|
+
logger.info("FTS search: %d results from BM25", len(fts_rows))
|
|
212
|
+
|
|
213
|
+
if mode == SearchMode.HYBRID and query_embedding:
|
|
214
|
+
logger.info("HYBRID merging FTS + vector results via RRF")
|
|
215
|
+
vec_limit = match_count * 10
|
|
216
|
+
vec_sql = f"""SELECT p.* FROM profiles p
|
|
217
|
+
WHERE {where_clause}
|
|
218
|
+
ORDER BY p.last_modified_timestamp DESC
|
|
219
|
+
LIMIT ?"""
|
|
220
|
+
vec_candidates = self._fetchall(vec_sql, (*params, vec_limit))
|
|
221
|
+
vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
|
|
222
|
+
rows = _true_rrf_merge(
|
|
223
|
+
fts_rows,
|
|
224
|
+
vec_rows,
|
|
225
|
+
"profile_id",
|
|
226
|
+
match_count,
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
rows = fts_rows
|
|
230
|
+
elif query_embedding:
|
|
231
|
+
# HYBRID without query text: rank by embedding only
|
|
232
|
+
if req.generated_from_request_id:
|
|
233
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
234
|
+
params.append(req.generated_from_request_id)
|
|
235
|
+
where_clause = " AND ".join(conditions)
|
|
236
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
237
|
+
WHERE {where_clause}
|
|
238
|
+
ORDER BY p.last_modified_timestamp DESC"""
|
|
239
|
+
rows = self._fetchall(sql, tuple(params))
|
|
240
|
+
logger.info(
|
|
241
|
+
"HYBRID (no query text) search: %d candidates, ranking by embedding",
|
|
242
|
+
len(rows),
|
|
243
|
+
)
|
|
244
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
245
|
+
else:
|
|
246
|
+
if req.generated_from_request_id:
|
|
247
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
248
|
+
params.append(req.generated_from_request_id)
|
|
249
|
+
where_clause = " AND ".join(conditions)
|
|
250
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
251
|
+
WHERE {where_clause}
|
|
252
|
+
ORDER BY p.last_modified_timestamp DESC
|
|
253
|
+
LIMIT ?"""
|
|
254
|
+
params_list = [*params, overfetch]
|
|
255
|
+
rows = self._fetchall(sql, tuple(params_list))
|
|
256
|
+
|
|
257
|
+
profiles = [_row_to_profile(r) for r in rows]
|
|
258
|
+
logger.info("Profile search: %d profiles before post-filtering", len(profiles))
|
|
259
|
+
|
|
260
|
+
# Apply filters that can't easily go into SQL
|
|
261
|
+
filtered: list[UserProfile] = []
|
|
262
|
+
for profile in profiles:
|
|
263
|
+
if req.custom_feature and (
|
|
264
|
+
req.custom_feature.lower() not in str(profile.custom_features).lower()
|
|
265
|
+
):
|
|
266
|
+
continue
|
|
267
|
+
filtered.append(profile)
|
|
268
|
+
if len(filtered) >= match_count:
|
|
269
|
+
break
|
|
270
|
+
return filtered
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from reflexio.models.api_schema.domain.enums import Status
|
|
2
2
|
|
|
3
|
+
from ._learning_jobs import LearningJob, LearningJobStatus, LearningJobStoreABC
|
|
3
4
|
from ._agent_run import (
|
|
4
5
|
NOT_APPLICABLE_ANSWER,
|
|
5
6
|
AgentBinding,
|
|
@@ -22,16 +23,22 @@ from ._agent_run import (
|
|
|
22
23
|
not_applicable_tool_result,
|
|
23
24
|
)
|
|
24
25
|
from ._base import BaseStorageCore, matches_status_filter
|
|
26
|
+
from ._commit_scope import CommitScopeMixin
|
|
25
27
|
from ._extras import ExtrasMixin
|
|
26
|
-
from ._governance import GovernanceMixin
|
|
27
28
|
from ._lineage import EntityType, LineageEventMixin
|
|
28
29
|
from ._operations import OperationMixin
|
|
29
|
-
from ._profiles import ProfileMixin
|
|
30
30
|
from ._requests import RequestMixin
|
|
31
31
|
from ._retrieval_log import RetrievalLogMixin
|
|
32
32
|
from ._shadow_verdicts import ShadowVerdictsMixin
|
|
33
33
|
from ._share_links import ShareLinkMixin
|
|
34
34
|
from ._stall_state import StallStateMixin
|
|
35
|
+
from .governance import (
|
|
36
|
+
AuditEventStoreMixin,
|
|
37
|
+
GovernanceEraseExecutionMixin,
|
|
38
|
+
PurgeOperationStoreMixin,
|
|
39
|
+
RebuildHideMixin,
|
|
40
|
+
SubjectBarrierMixin,
|
|
41
|
+
)
|
|
35
42
|
from .playbook import (
|
|
36
43
|
AgentEvaluationResultStoreMixin,
|
|
37
44
|
AgentPlaybookStoreMixin,
|
|
@@ -39,11 +46,16 @@ from .playbook import (
|
|
|
39
46
|
PlaybookSourceLinkageMixin,
|
|
40
47
|
UserPlaybookStoreMixin,
|
|
41
48
|
)
|
|
49
|
+
from .profiles import InteractionStoreMixin, ProfileSearchMixin, ProfileStoreMixin
|
|
42
50
|
|
|
43
51
|
|
|
44
52
|
class BaseStorage(
|
|
53
|
+
LearningJobStoreABC,
|
|
54
|
+
CommitScopeMixin,
|
|
45
55
|
AgentRunMixin,
|
|
46
|
-
|
|
56
|
+
ProfileStoreMixin,
|
|
57
|
+
InteractionStoreMixin,
|
|
58
|
+
ProfileSearchMixin,
|
|
47
59
|
RequestMixin,
|
|
48
60
|
AgentPlaybookStoreMixin,
|
|
49
61
|
UserPlaybookStoreMixin,
|
|
@@ -51,7 +63,11 @@ class BaseStorage(
|
|
|
51
63
|
OptimizationJobStoreMixin,
|
|
52
64
|
AgentEvaluationResultStoreMixin,
|
|
53
65
|
RetrievalLogMixin,
|
|
54
|
-
|
|
66
|
+
AuditEventStoreMixin,
|
|
67
|
+
PurgeOperationStoreMixin,
|
|
68
|
+
SubjectBarrierMixin,
|
|
69
|
+
GovernanceEraseExecutionMixin,
|
|
70
|
+
RebuildHideMixin,
|
|
55
71
|
LineageEventMixin,
|
|
56
72
|
OperationMixin,
|
|
57
73
|
ExtrasMixin,
|
|
@@ -162,8 +178,8 @@ class BaseStorage(
|
|
|
162
178
|
interaction_count = len(self.get_user_interaction(user_id))
|
|
163
179
|
|
|
164
180
|
# All statuses a user's row can have — including tombstones (SUPERSEDED,
|
|
165
|
-
# MERGED). Erasure MUST reach every row the user owns regardless
|
|
166
|
-
# status; the old filter excluded tombstones, leaving them in the DB
|
|
181
|
+
# MERGED, EXPIRED). Erasure MUST reach every row the user owns regardless
|
|
182
|
+
# of status; the old filter excluded tombstones, leaving them in the DB
|
|
167
183
|
# after clear_user_data (GDPR regression).
|
|
168
184
|
_all_statuses: list[Status | None] = [
|
|
169
185
|
None, # CURRENT
|
|
@@ -172,6 +188,7 @@ class BaseStorage(
|
|
|
172
188
|
Status.ARCHIVE_IN_PROGRESS,
|
|
173
189
|
Status.SUPERSEDED,
|
|
174
190
|
Status.MERGED,
|
|
191
|
+
Status.EXPIRED,
|
|
175
192
|
]
|
|
176
193
|
|
|
177
194
|
# Snapshot user_playbook ids for the user and partition into
|
|
@@ -190,10 +207,14 @@ class BaseStorage(
|
|
|
190
207
|
)
|
|
191
208
|
|
|
192
209
|
# Snapshot profile ids for the user and partition into
|
|
193
|
-
# purge vs delete sets.
|
|
210
|
+
# purge vs delete sets. include_expired=True drops the
|
|
211
|
+
# expiration_timestamp >= now guard so EXPIRED tombstones (expiry in
|
|
212
|
+
# the past) are enumerated and reached by the erasure path.
|
|
194
213
|
raw_profile_ids = [
|
|
195
214
|
p.profile_id
|
|
196
|
-
for p in self.get_user_profile(
|
|
215
|
+
for p in self.get_user_profile(
|
|
216
|
+
user_id, status_filter=_all_statuses, include_expired=True
|
|
217
|
+
)
|
|
197
218
|
if p.profile_id is not None
|
|
198
219
|
]
|
|
199
220
|
purge_profile_ids, delete_profile_ids = self._partition_purge_vs_delete(
|
|
@@ -243,8 +264,21 @@ class BaseStorage(
|
|
|
243
264
|
"purged_user_playbooks": len(purge_upb_ids),
|
|
244
265
|
}
|
|
245
266
|
|
|
267
|
+
def learning_jobs_columns(self) -> list[str]:
|
|
268
|
+
"""Return the column names of the learning_jobs table.
|
|
269
|
+
|
|
270
|
+
Each backend overrides this to query its own schema introspection
|
|
271
|
+
mechanism (SQLite: PRAGMA table_info; Postgres/Supabase:
|
|
272
|
+
information_schema.columns).
|
|
273
|
+
"""
|
|
274
|
+
raise NotImplementedError
|
|
275
|
+
|
|
246
276
|
|
|
247
277
|
__all__ = [
|
|
278
|
+
"LearningJob",
|
|
279
|
+
"LearningJobStatus",
|
|
280
|
+
"LearningJobStoreABC",
|
|
281
|
+
"CommitScopeMixin",
|
|
248
282
|
"AgentBinding",
|
|
249
283
|
"AgentRunMixin",
|
|
250
284
|
"EntityType",
|
|
@@ -257,10 +291,18 @@ __all__ = [
|
|
|
257
291
|
"PendingToolCallStatus",
|
|
258
292
|
"PendingToolCallUpsertResult",
|
|
259
293
|
"AgentEvaluationResultStoreMixin",
|
|
294
|
+
"AuditEventStoreMixin",
|
|
295
|
+
"PurgeOperationStoreMixin",
|
|
296
|
+
"SubjectBarrierMixin",
|
|
297
|
+
"GovernanceEraseExecutionMixin",
|
|
298
|
+
"RebuildHideMixin",
|
|
260
299
|
"AgentPlaybookStoreMixin",
|
|
261
300
|
"OptimizationJobStoreMixin",
|
|
262
301
|
"PlaybookSourceLinkageMixin",
|
|
263
302
|
"UserPlaybookStoreMixin",
|
|
303
|
+
"InteractionStoreMixin",
|
|
304
|
+
"ProfileSearchMixin",
|
|
305
|
+
"ProfileStoreMixin",
|
|
264
306
|
"RetrievalLogMixin",
|
|
265
307
|
"PriorAnswerMatch",
|
|
266
308
|
"RunToolDependencyKind",
|