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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Agent success evaluation result store methods for SQLite storage."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
7
|
+
AgentSuccessEvaluationResult,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from .._base import (
|
|
11
|
+
SQLiteStorageBase,
|
|
12
|
+
_epoch_to_iso,
|
|
13
|
+
_json_dumps,
|
|
14
|
+
_row_to_eval_result,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentEvaluationResultStoreMixin:
|
|
19
|
+
"""Mixin providing agent success evaluation result CRUD for SQLite storage."""
|
|
20
|
+
|
|
21
|
+
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
22
|
+
_lock: Any
|
|
23
|
+
conn: sqlite3.Connection
|
|
24
|
+
_execute: Any
|
|
25
|
+
_fetchall: Any
|
|
26
|
+
_get_embedding: Any
|
|
27
|
+
_subject_ref_for_user_id: Any
|
|
28
|
+
_assert_subject_writable_locked: Any
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
# Agent Success Evaluation methods
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
@SQLiteStorageBase.handle_exceptions
|
|
35
|
+
def save_agent_success_evaluation_results(
|
|
36
|
+
self, results: list[AgentSuccessEvaluationResult]
|
|
37
|
+
) -> None:
|
|
38
|
+
for result in results:
|
|
39
|
+
embedding_text = f"{result.failure_type} {result.failure_reason}"
|
|
40
|
+
if embedding_text.strip():
|
|
41
|
+
result.embedding = self._get_embedding(embedding_text)
|
|
42
|
+
else:
|
|
43
|
+
result.embedding = []
|
|
44
|
+
|
|
45
|
+
created_at_iso = _epoch_to_iso(result.created_at)
|
|
46
|
+
subject_ref = self._subject_ref_for_user_id(result.user_id)
|
|
47
|
+
with self._lock:
|
|
48
|
+
try:
|
|
49
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
50
|
+
self._assert_subject_writable_locked(subject_ref)
|
|
51
|
+
self.conn.execute(
|
|
52
|
+
"""INSERT INTO agent_success_evaluation_result
|
|
53
|
+
(user_id, session_id, agent_version, evaluation_name, is_success,
|
|
54
|
+
failure_type, failure_reason, regular_vs_shadow,
|
|
55
|
+
number_of_correction_per_session, user_turns_to_resolution,
|
|
56
|
+
is_escalated, embedding, created_at, governance_subject_ref)
|
|
57
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
58
|
+
(
|
|
59
|
+
result.user_id,
|
|
60
|
+
result.session_id,
|
|
61
|
+
result.agent_version,
|
|
62
|
+
result.evaluation_name,
|
|
63
|
+
int(result.is_success),
|
|
64
|
+
result.failure_type,
|
|
65
|
+
result.failure_reason,
|
|
66
|
+
result.regular_vs_shadow.value
|
|
67
|
+
if result.regular_vs_shadow
|
|
68
|
+
else None,
|
|
69
|
+
result.number_of_correction_per_session,
|
|
70
|
+
result.user_turns_to_resolution,
|
|
71
|
+
int(result.is_escalated),
|
|
72
|
+
_json_dumps(result.embedding) if result.embedding else None,
|
|
73
|
+
created_at_iso,
|
|
74
|
+
subject_ref,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
self.conn.commit()
|
|
78
|
+
except Exception:
|
|
79
|
+
self.conn.rollback()
|
|
80
|
+
raise
|
|
81
|
+
|
|
82
|
+
@SQLiteStorageBase.handle_exceptions
|
|
83
|
+
def get_agent_success_evaluation_results(
|
|
84
|
+
self, limit: int = 100, agent_version: str | None = None
|
|
85
|
+
) -> list[AgentSuccessEvaluationResult]:
|
|
86
|
+
sql = "SELECT * FROM agent_success_evaluation_result"
|
|
87
|
+
params: list[Any] = []
|
|
88
|
+
if agent_version is not None:
|
|
89
|
+
sql += " WHERE agent_version = ?"
|
|
90
|
+
params.append(agent_version)
|
|
91
|
+
sql += " ORDER BY created_at DESC LIMIT ?"
|
|
92
|
+
params.append(limit)
|
|
93
|
+
rows = self._fetchall(sql, params)
|
|
94
|
+
return [_row_to_eval_result(r) for r in rows]
|
|
95
|
+
|
|
96
|
+
@SQLiteStorageBase.handle_exceptions
|
|
97
|
+
def get_agent_success_evaluation_results_in_window(
|
|
98
|
+
self,
|
|
99
|
+
from_ts: int,
|
|
100
|
+
to_ts: int,
|
|
101
|
+
agent_version: str | None = None,
|
|
102
|
+
limit: int | None = None,
|
|
103
|
+
) -> list[AgentSuccessEvaluationResult]:
|
|
104
|
+
sql = """SELECT * FROM agent_success_evaluation_result
|
|
105
|
+
WHERE created_at >= ? AND created_at <= ?"""
|
|
106
|
+
params: list[Any] = [_epoch_to_iso(from_ts), _epoch_to_iso(to_ts)]
|
|
107
|
+
if agent_version is not None:
|
|
108
|
+
sql += " AND agent_version = ?"
|
|
109
|
+
params.append(agent_version)
|
|
110
|
+
sql += " ORDER BY created_at DESC"
|
|
111
|
+
if limit is not None:
|
|
112
|
+
sql += " LIMIT ?"
|
|
113
|
+
params.append(limit)
|
|
114
|
+
rows = self._fetchall(sql, params)
|
|
115
|
+
return [_row_to_eval_result(r) for r in rows]
|
|
116
|
+
|
|
117
|
+
@SQLiteStorageBase.handle_exceptions
|
|
118
|
+
def get_agent_success_evaluation_result_ids(
|
|
119
|
+
self,
|
|
120
|
+
user_id: str,
|
|
121
|
+
session_id: str,
|
|
122
|
+
evaluation_name: str,
|
|
123
|
+
agent_version: str,
|
|
124
|
+
) -> list[int]:
|
|
125
|
+
rows = self._fetchall(
|
|
126
|
+
"""SELECT result_id FROM agent_success_evaluation_result
|
|
127
|
+
WHERE user_id = ?
|
|
128
|
+
AND session_id = ?
|
|
129
|
+
AND evaluation_name = ?
|
|
130
|
+
AND agent_version = ?
|
|
131
|
+
ORDER BY created_at DESC""",
|
|
132
|
+
(user_id, session_id, evaluation_name, agent_version),
|
|
133
|
+
)
|
|
134
|
+
return [int(r["result_id"]) for r in rows]
|
|
135
|
+
|
|
136
|
+
@SQLiteStorageBase.handle_exceptions
|
|
137
|
+
def delete_all_agent_success_evaluation_results(self) -> None:
|
|
138
|
+
self._execute("DELETE FROM agent_success_evaluation_result")
|
|
139
|
+
|
|
140
|
+
@SQLiteStorageBase.handle_exceptions
|
|
141
|
+
def delete_agent_success_evaluation_results_for_session(
|
|
142
|
+
self,
|
|
143
|
+
user_id: str,
|
|
144
|
+
session_id: str,
|
|
145
|
+
evaluation_name: str,
|
|
146
|
+
agent_version: str,
|
|
147
|
+
) -> int:
|
|
148
|
+
"""Delete results scoped to (user_id, session_id, evaluation_name, agent_version).
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
user_id (str): User whose session results to clear.
|
|
152
|
+
session_id (str): Session whose results to clear.
|
|
153
|
+
evaluation_name (str): Which evaluator's results to clear.
|
|
154
|
+
agent_version (str): Agent version scope.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
int: Number of rows deleted.
|
|
158
|
+
"""
|
|
159
|
+
cur = self._execute(
|
|
160
|
+
"""DELETE FROM agent_success_evaluation_result
|
|
161
|
+
WHERE user_id = ?
|
|
162
|
+
AND session_id = ?
|
|
163
|
+
AND evaluation_name = ?
|
|
164
|
+
AND agent_version = ?""",
|
|
165
|
+
(user_id, session_id, evaluation_name, agent_version),
|
|
166
|
+
)
|
|
167
|
+
return cur.rowcount
|
|
168
|
+
|
|
169
|
+
@SQLiteStorageBase.handle_exceptions
|
|
170
|
+
def delete_agent_success_evaluation_results_by_ids(
|
|
171
|
+
self, result_ids: list[int]
|
|
172
|
+
) -> int:
|
|
173
|
+
"""Delete agent success eval result rows by primary key.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
result_ids (list[int]): Primary-key result_ids to delete. An empty
|
|
177
|
+
list is a no-op that returns 0.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
int: Number of rows actually deleted (ignores non-existent ids).
|
|
181
|
+
"""
|
|
182
|
+
if not result_ids:
|
|
183
|
+
return 0
|
|
184
|
+
placeholders = ",".join(["?"] * len(result_ids))
|
|
185
|
+
cur = self._execute(
|
|
186
|
+
f"DELETE FROM agent_success_evaluation_result WHERE result_id IN ({placeholders})",
|
|
187
|
+
list(result_ids),
|
|
188
|
+
)
|
|
189
|
+
return cur.rowcount
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Playbook optimization job store methods for SQLite storage."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
7
|
+
PlaybookOptimizationCandidate,
|
|
8
|
+
PlaybookOptimizationEvaluation,
|
|
9
|
+
PlaybookOptimizationEvent,
|
|
10
|
+
PlaybookOptimizationJob,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .._base import (
|
|
14
|
+
SQLiteStorageBase,
|
|
15
|
+
_json_dumps,
|
|
16
|
+
_json_loads,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _row_to_playbook_optimization_candidate(
|
|
21
|
+
row: sqlite3.Row,
|
|
22
|
+
) -> PlaybookOptimizationCandidate:
|
|
23
|
+
return PlaybookOptimizationCandidate(
|
|
24
|
+
candidate_id=row["candidate_id"],
|
|
25
|
+
job_id=row["job_id"],
|
|
26
|
+
candidate_index=row["candidate_index"],
|
|
27
|
+
content=row["content"],
|
|
28
|
+
parent_candidate_ids=_json_loads(row["parent_candidate_ids"]) or [],
|
|
29
|
+
aggregate_score=row["aggregate_score"],
|
|
30
|
+
is_winner=bool(row["is_winner"]),
|
|
31
|
+
metadata_json=row["metadata_json"] or "{}",
|
|
32
|
+
created_at=row["created_at"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _row_to_playbook_optimization_evaluation(
|
|
37
|
+
row: sqlite3.Row,
|
|
38
|
+
) -> PlaybookOptimizationEvaluation:
|
|
39
|
+
return PlaybookOptimizationEvaluation(
|
|
40
|
+
evaluation_id=row["evaluation_id"],
|
|
41
|
+
job_id=row["job_id"],
|
|
42
|
+
candidate_id=row["candidate_id"],
|
|
43
|
+
target_kind=row["target_kind"],
|
|
44
|
+
target_id=row["target_id"],
|
|
45
|
+
scenario_user_playbook_id=row["scenario_user_playbook_id"],
|
|
46
|
+
source_interaction_ids=_json_loads(row["source_interaction_ids"]) or [],
|
|
47
|
+
score=row["score"],
|
|
48
|
+
verdict=row["verdict"],
|
|
49
|
+
likert=row["likert"],
|
|
50
|
+
rationale=row["rationale"],
|
|
51
|
+
asi_json=row["asi_json"],
|
|
52
|
+
incumbent_rollout_json=row["incumbent_rollout_json"],
|
|
53
|
+
candidate_rollout_json=row["candidate_rollout_json"],
|
|
54
|
+
created_at=row["created_at"],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class OptimizationJobStoreMixin:
|
|
59
|
+
"""Mixin providing playbook optimization job/candidate/evaluation CRUD for SQLite storage."""
|
|
60
|
+
|
|
61
|
+
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
62
|
+
_lock: Any
|
|
63
|
+
conn: sqlite3.Connection
|
|
64
|
+
_execute: Any
|
|
65
|
+
_fetchall: Any
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Playbook optimizer methods
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
@SQLiteStorageBase.handle_exceptions
|
|
72
|
+
def create_playbook_optimization_job(
|
|
73
|
+
self, job: PlaybookOptimizationJob
|
|
74
|
+
) -> PlaybookOptimizationJob:
|
|
75
|
+
with self._lock:
|
|
76
|
+
cur = self.conn.execute(
|
|
77
|
+
"""INSERT INTO playbook_optimization_jobs
|
|
78
|
+
(target_kind, target_id, status, best_candidate_id,
|
|
79
|
+
successor_target_id, decision_reason, metadata_json,
|
|
80
|
+
created_at, updated_at)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
82
|
+
(
|
|
83
|
+
job.target_kind,
|
|
84
|
+
job.target_id,
|
|
85
|
+
job.status,
|
|
86
|
+
job.best_candidate_id,
|
|
87
|
+
job.successor_target_id,
|
|
88
|
+
job.decision_reason,
|
|
89
|
+
job.metadata_json,
|
|
90
|
+
job.created_at,
|
|
91
|
+
job.updated_at,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
job.job_id = cur.lastrowid or 0
|
|
95
|
+
self.conn.commit()
|
|
96
|
+
return job
|
|
97
|
+
|
|
98
|
+
@SQLiteStorageBase.handle_exceptions
|
|
99
|
+
def update_playbook_optimization_job(
|
|
100
|
+
self,
|
|
101
|
+
job_id: int,
|
|
102
|
+
*,
|
|
103
|
+
status: str | None = None,
|
|
104
|
+
best_candidate_id: int | None = None,
|
|
105
|
+
successor_target_id: int | None = None,
|
|
106
|
+
decision_reason: str | None = None,
|
|
107
|
+
metadata_json: str | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
updates: list[str] = ["updated_at = strftime('%s','now')"]
|
|
110
|
+
params: list[Any] = []
|
|
111
|
+
if status is not None:
|
|
112
|
+
updates.append("status = ?")
|
|
113
|
+
params.append(status)
|
|
114
|
+
if best_candidate_id is not None:
|
|
115
|
+
updates.append("best_candidate_id = ?")
|
|
116
|
+
params.append(best_candidate_id)
|
|
117
|
+
if successor_target_id is not None:
|
|
118
|
+
updates.append("successor_target_id = ?")
|
|
119
|
+
params.append(successor_target_id)
|
|
120
|
+
if decision_reason is not None:
|
|
121
|
+
updates.append("decision_reason = ?")
|
|
122
|
+
params.append(decision_reason)
|
|
123
|
+
if metadata_json is not None:
|
|
124
|
+
updates.append("metadata_json = ?")
|
|
125
|
+
params.append(metadata_json)
|
|
126
|
+
params.append(job_id)
|
|
127
|
+
self._execute(
|
|
128
|
+
f"UPDATE playbook_optimization_jobs SET {', '.join(updates)} WHERE job_id = ?", # noqa: S608
|
|
129
|
+
tuple(params),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@SQLiteStorageBase.handle_exceptions
|
|
133
|
+
def insert_playbook_optimization_candidate(
|
|
134
|
+
self, candidate: PlaybookOptimizationCandidate
|
|
135
|
+
) -> PlaybookOptimizationCandidate:
|
|
136
|
+
with self._lock:
|
|
137
|
+
cur = self.conn.execute(
|
|
138
|
+
"""INSERT INTO playbook_optimization_candidates
|
|
139
|
+
(job_id, candidate_index, content, parent_candidate_ids,
|
|
140
|
+
aggregate_score, is_winner, metadata_json, created_at)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
142
|
+
(
|
|
143
|
+
candidate.job_id,
|
|
144
|
+
candidate.candidate_index,
|
|
145
|
+
candidate.content,
|
|
146
|
+
_json_dumps(candidate.parent_candidate_ids) or "[]",
|
|
147
|
+
candidate.aggregate_score,
|
|
148
|
+
1 if candidate.is_winner else 0,
|
|
149
|
+
candidate.metadata_json,
|
|
150
|
+
candidate.created_at,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
candidate.candidate_id = cur.lastrowid or 0
|
|
154
|
+
self.conn.commit()
|
|
155
|
+
return candidate
|
|
156
|
+
|
|
157
|
+
@SQLiteStorageBase.handle_exceptions
|
|
158
|
+
def list_playbook_optimization_candidates(
|
|
159
|
+
self, job_id: int
|
|
160
|
+
) -> list[PlaybookOptimizationCandidate]:
|
|
161
|
+
rows = self._fetchall(
|
|
162
|
+
"SELECT * FROM playbook_optimization_candidates WHERE job_id = ? ORDER BY candidate_id ASC",
|
|
163
|
+
(job_id,),
|
|
164
|
+
)
|
|
165
|
+
return [_row_to_playbook_optimization_candidate(row) for row in rows]
|
|
166
|
+
|
|
167
|
+
@SQLiteStorageBase.handle_exceptions
|
|
168
|
+
def update_playbook_optimization_candidate(
|
|
169
|
+
self,
|
|
170
|
+
candidate_id: int,
|
|
171
|
+
*,
|
|
172
|
+
aggregate_score: float | None = None,
|
|
173
|
+
is_winner: bool | None = None,
|
|
174
|
+
) -> None:
|
|
175
|
+
updates: list[str] = []
|
|
176
|
+
params: list[Any] = []
|
|
177
|
+
if aggregate_score is not None:
|
|
178
|
+
updates.append("aggregate_score = ?")
|
|
179
|
+
params.append(aggregate_score)
|
|
180
|
+
if is_winner is not None:
|
|
181
|
+
updates.append("is_winner = ?")
|
|
182
|
+
params.append(1 if is_winner else 0)
|
|
183
|
+
if not updates:
|
|
184
|
+
return
|
|
185
|
+
params.append(candidate_id)
|
|
186
|
+
self._execute(
|
|
187
|
+
f"UPDATE playbook_optimization_candidates SET {', '.join(updates)} WHERE candidate_id = ?", # noqa: S608
|
|
188
|
+
tuple(params),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@SQLiteStorageBase.handle_exceptions
|
|
192
|
+
def insert_playbook_optimization_evaluation(
|
|
193
|
+
self, evaluation: PlaybookOptimizationEvaluation
|
|
194
|
+
) -> PlaybookOptimizationEvaluation:
|
|
195
|
+
with self._lock:
|
|
196
|
+
cur = self.conn.execute(
|
|
197
|
+
"""INSERT INTO playbook_optimization_evaluations
|
|
198
|
+
(job_id, candidate_id, target_kind, target_id,
|
|
199
|
+
scenario_user_playbook_id, source_interaction_ids, score,
|
|
200
|
+
verdict, likert, rationale, asi_json, incumbent_rollout_json,
|
|
201
|
+
candidate_rollout_json, created_at)
|
|
202
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
203
|
+
(
|
|
204
|
+
evaluation.job_id,
|
|
205
|
+
evaluation.candidate_id,
|
|
206
|
+
evaluation.target_kind,
|
|
207
|
+
evaluation.target_id,
|
|
208
|
+
evaluation.scenario_user_playbook_id,
|
|
209
|
+
_json_dumps(evaluation.source_interaction_ids) or "[]",
|
|
210
|
+
evaluation.score,
|
|
211
|
+
evaluation.verdict,
|
|
212
|
+
evaluation.likert,
|
|
213
|
+
evaluation.rationale,
|
|
214
|
+
evaluation.asi_json,
|
|
215
|
+
evaluation.incumbent_rollout_json,
|
|
216
|
+
evaluation.candidate_rollout_json,
|
|
217
|
+
evaluation.created_at,
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
evaluation.evaluation_id = cur.lastrowid or 0
|
|
221
|
+
self.conn.commit()
|
|
222
|
+
return evaluation
|
|
223
|
+
|
|
224
|
+
@SQLiteStorageBase.handle_exceptions
|
|
225
|
+
def list_playbook_optimization_evaluations(
|
|
226
|
+
self, job_id: int
|
|
227
|
+
) -> list[PlaybookOptimizationEvaluation]:
|
|
228
|
+
rows = self._fetchall(
|
|
229
|
+
"SELECT * FROM playbook_optimization_evaluations WHERE job_id = ? ORDER BY evaluation_id ASC",
|
|
230
|
+
(job_id,),
|
|
231
|
+
)
|
|
232
|
+
return [_row_to_playbook_optimization_evaluation(row) for row in rows]
|
|
233
|
+
|
|
234
|
+
@SQLiteStorageBase.handle_exceptions
|
|
235
|
+
def insert_playbook_optimization_event(
|
|
236
|
+
self, event: PlaybookOptimizationEvent
|
|
237
|
+
) -> PlaybookOptimizationEvent:
|
|
238
|
+
with self._lock:
|
|
239
|
+
cur = self.conn.execute(
|
|
240
|
+
"""INSERT INTO playbook_optimization_events
|
|
241
|
+
(job_id, event_type, payload_json, created_at)
|
|
242
|
+
VALUES (?, ?, ?, ?)""",
|
|
243
|
+
(event.job_id, event.event_type, event.payload_json, event.created_at),
|
|
244
|
+
)
|
|
245
|
+
event.event_id = cur.lastrowid or 0
|
|
246
|
+
self.conn.commit()
|
|
247
|
+
return event
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Agent playbook source-linkage methods for SQLite storage."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from reflexio.models.api_schema.service_schemas import AgentPlaybookSourceWindow
|
|
8
|
+
|
|
9
|
+
from .._base import (
|
|
10
|
+
SQLiteStorageBase,
|
|
11
|
+
_json_dumps,
|
|
12
|
+
_json_loads,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PlaybookSourceLinkageMixin:
|
|
17
|
+
"""Mixin providing agent playbook source-linkage CRUD for SQLite storage."""
|
|
18
|
+
|
|
19
|
+
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
20
|
+
_lock: Any
|
|
21
|
+
conn: sqlite3.Connection
|
|
22
|
+
_fetchall: Any
|
|
23
|
+
_subject_ref_for_user_id: Any
|
|
24
|
+
_assert_subject_writable_locked: Any
|
|
25
|
+
|
|
26
|
+
@SQLiteStorageBase.handle_exceptions
|
|
27
|
+
def set_source_user_playbook_ids_for_agent_playbook(
|
|
28
|
+
self, agent_playbook_id: int, user_playbook_ids: list[int]
|
|
29
|
+
) -> None:
|
|
30
|
+
self.set_source_windows_for_agent_playbook(
|
|
31
|
+
agent_playbook_id,
|
|
32
|
+
[
|
|
33
|
+
AgentPlaybookSourceWindow(
|
|
34
|
+
user_playbook_id=upid, source_interaction_ids=[]
|
|
35
|
+
)
|
|
36
|
+
for upid in user_playbook_ids
|
|
37
|
+
],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@SQLiteStorageBase.handle_exceptions
|
|
41
|
+
def get_source_user_playbook_ids_for_agent_playbook(
|
|
42
|
+
self, agent_playbook_id: int
|
|
43
|
+
) -> list[int]:
|
|
44
|
+
return [
|
|
45
|
+
window.user_playbook_id
|
|
46
|
+
for window in self.get_source_windows_for_agent_playbook(agent_playbook_id)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
@SQLiteStorageBase.handle_exceptions
|
|
50
|
+
def get_source_user_playbook_ids_for_agent_playbooks(
|
|
51
|
+
self, agent_playbook_ids: Sequence[int]
|
|
52
|
+
) -> dict[int, list[int]]:
|
|
53
|
+
if not agent_playbook_ids:
|
|
54
|
+
return {}
|
|
55
|
+
unique_ids = list(dict.fromkeys(int(apid) for apid in agent_playbook_ids))
|
|
56
|
+
ph = ",".join("?" for _ in unique_ids)
|
|
57
|
+
rows = self._fetchall(
|
|
58
|
+
f"""SELECT agent_playbook_id, user_playbook_id
|
|
59
|
+
FROM agent_playbook_source_user_playbooks
|
|
60
|
+
WHERE agent_playbook_id IN ({ph})
|
|
61
|
+
ORDER BY agent_playbook_id ASC, user_playbook_id ASC""",
|
|
62
|
+
unique_ids,
|
|
63
|
+
)
|
|
64
|
+
by_agent_id: dict[int, list[int]] = {apid: [] for apid in unique_ids}
|
|
65
|
+
seen_by_agent_id: dict[int, set[int]] = {apid: set() for apid in unique_ids}
|
|
66
|
+
for row in rows:
|
|
67
|
+
agent_playbook_id = int(row["agent_playbook_id"])
|
|
68
|
+
user_playbook_id = int(row["user_playbook_id"])
|
|
69
|
+
seen = seen_by_agent_id.setdefault(agent_playbook_id, set())
|
|
70
|
+
if user_playbook_id not in seen:
|
|
71
|
+
by_agent_id.setdefault(agent_playbook_id, []).append(user_playbook_id)
|
|
72
|
+
seen.add(user_playbook_id)
|
|
73
|
+
return by_agent_id
|
|
74
|
+
|
|
75
|
+
@SQLiteStorageBase.handle_exceptions
|
|
76
|
+
def set_source_windows_for_agent_playbook(
|
|
77
|
+
self,
|
|
78
|
+
agent_playbook_id: int,
|
|
79
|
+
source_windows: list[AgentPlaybookSourceWindow],
|
|
80
|
+
) -> None:
|
|
81
|
+
by_id: dict[int, list[int]] = {}
|
|
82
|
+
for window in source_windows:
|
|
83
|
+
ids = by_id.setdefault(window.user_playbook_id, [])
|
|
84
|
+
seen = set(ids)
|
|
85
|
+
for source_id in window.source_interaction_ids:
|
|
86
|
+
if source_id not in seen:
|
|
87
|
+
ids.append(source_id)
|
|
88
|
+
seen.add(source_id)
|
|
89
|
+
with self._lock:
|
|
90
|
+
try:
|
|
91
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
92
|
+
for user_playbook_id in by_id:
|
|
93
|
+
row = self.conn.execute(
|
|
94
|
+
"""SELECT user_id, governance_subject_ref FROM user_playbooks
|
|
95
|
+
WHERE user_playbook_id = ?""",
|
|
96
|
+
(user_playbook_id,),
|
|
97
|
+
).fetchone()
|
|
98
|
+
if row is None:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"User playbook {user_playbook_id} not found for source window"
|
|
101
|
+
)
|
|
102
|
+
subject_ref = row["governance_subject_ref"]
|
|
103
|
+
if not isinstance(subject_ref, str) or not subject_ref:
|
|
104
|
+
subject_ref = self._subject_ref_for_user_id(str(row["user_id"]))
|
|
105
|
+
self._assert_subject_writable_locked(subject_ref)
|
|
106
|
+
self.conn.execute(
|
|
107
|
+
"DELETE FROM agent_playbook_source_user_playbooks WHERE agent_playbook_id = ?",
|
|
108
|
+
(agent_playbook_id,),
|
|
109
|
+
)
|
|
110
|
+
self.conn.executemany(
|
|
111
|
+
"""INSERT OR IGNORE INTO agent_playbook_source_user_playbooks
|
|
112
|
+
(agent_playbook_id, user_playbook_id, source_interaction_ids)
|
|
113
|
+
VALUES (?, ?, ?)""",
|
|
114
|
+
[
|
|
115
|
+
(
|
|
116
|
+
agent_playbook_id,
|
|
117
|
+
upid,
|
|
118
|
+
_json_dumps(source_interaction_ids) or "[]",
|
|
119
|
+
)
|
|
120
|
+
for upid, source_interaction_ids in by_id.items()
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
self.conn.commit()
|
|
124
|
+
except Exception:
|
|
125
|
+
self.conn.rollback()
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
@SQLiteStorageBase.handle_exceptions
|
|
129
|
+
def get_source_windows_for_agent_playbook(
|
|
130
|
+
self, agent_playbook_id: int
|
|
131
|
+
) -> list[AgentPlaybookSourceWindow]:
|
|
132
|
+
rows = self._fetchall(
|
|
133
|
+
"""SELECT user_playbook_id, source_interaction_ids
|
|
134
|
+
FROM agent_playbook_source_user_playbooks
|
|
135
|
+
WHERE agent_playbook_id = ?
|
|
136
|
+
ORDER BY user_playbook_id ASC""",
|
|
137
|
+
(agent_playbook_id,),
|
|
138
|
+
)
|
|
139
|
+
return [
|
|
140
|
+
AgentPlaybookSourceWindow(
|
|
141
|
+
user_playbook_id=int(row["user_playbook_id"]),
|
|
142
|
+
source_interaction_ids=_json_loads(row["source_interaction_ids"]) or [],
|
|
143
|
+
)
|
|
144
|
+
for row in rows
|
|
145
|
+
]
|