claude-smart 0.2.49 → 0.2.50
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 +2 -2
- package/README.md +10 -43
- package/bin/claude-smart.js +105 -0
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +197 -1
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +757 -119
- package/plugin/scripts/codex-hook.js +63 -225
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +117 -136
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/smart-install.sh +15 -1
- package/plugin/src/claude_smart/cli.py +14 -0
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +4 -1
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
|
@@ -8,7 +8,8 @@ messaging without peeking at the adapter.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any, Literal
|
|
12
13
|
|
|
13
14
|
from claude_smart import state
|
|
14
15
|
from claude_smart.reflexio_adapter import Adapter
|
|
@@ -16,6 +17,50 @@ from claude_smart.reflexio_adapter import Adapter
|
|
|
16
17
|
PublishStatus = Literal["nothing", "ok", "failed"]
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _publish_request_id(session_id: str, start: int, end: int) -> str:
|
|
21
|
+
"""Return a stable request ID for one buffered publish range."""
|
|
22
|
+
|
|
23
|
+
name = f"claude-smart:{session_id}:{start}:{end}"
|
|
24
|
+
return str(uuid.uuid5(uuid.NAMESPACE_URL, name))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _prepare_publish_batch(
|
|
28
|
+
session_id: str,
|
|
29
|
+
) -> tuple[int, int, list[dict[str, Any]]] | None:
|
|
30
|
+
"""Freeze and return one canonical unpublished record range."""
|
|
31
|
+
|
|
32
|
+
while True:
|
|
33
|
+
records = state.read_all(session_id)
|
|
34
|
+
published, available = state.unpublished_slice(records)
|
|
35
|
+
if not available:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
publish_end = state.pending_publish_end(records, published)
|
|
39
|
+
if publish_end is None:
|
|
40
|
+
publish_end = state.safe_publish_end(records, published)
|
|
41
|
+
_, proposed = state.unpublished_slice(
|
|
42
|
+
records[:publish_end], published_override=published
|
|
43
|
+
)
|
|
44
|
+
if not proposed:
|
|
45
|
+
return None
|
|
46
|
+
state.append(
|
|
47
|
+
session_id,
|
|
48
|
+
{"publish_attempt": {"start": published, "end": publish_end}},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
canonical = state.read_all(session_id)
|
|
52
|
+
if state.published_record_offset(canonical) != published:
|
|
53
|
+
continue
|
|
54
|
+
canonical_end = state.pending_publish_end(canonical, published)
|
|
55
|
+
if canonical_end is None:
|
|
56
|
+
continue
|
|
57
|
+
_, interactions = state.unpublished_slice(
|
|
58
|
+
canonical[:canonical_end], published_override=published
|
|
59
|
+
)
|
|
60
|
+
if interactions:
|
|
61
|
+
return published, canonical_end, interactions
|
|
62
|
+
|
|
63
|
+
|
|
19
64
|
def publish_unpublished(
|
|
20
65
|
*,
|
|
21
66
|
session_id: str,
|
|
@@ -56,20 +101,27 @@ def publish_unpublished(
|
|
|
56
101
|
was unreachable. On ``"failed"`` the watermark is not advanced,
|
|
57
102
|
so the next hook retries the same batch.
|
|
58
103
|
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if not interactions:
|
|
104
|
+
batch = _prepare_publish_batch(session_id)
|
|
105
|
+
if batch is None:
|
|
62
106
|
return ("nothing", 0)
|
|
107
|
+
published_record_offset, publish_end, interactions = batch
|
|
108
|
+
|
|
63
109
|
client = adapter if adapter is not None else Adapter()
|
|
64
|
-
|
|
110
|
+
request_id = _publish_request_id(session_id, published_record_offset, publish_end)
|
|
111
|
+
result = client.publish(
|
|
65
112
|
session_id=session_id,
|
|
66
113
|
project_id=project_id,
|
|
114
|
+
request_id=request_id,
|
|
67
115
|
interactions=interactions,
|
|
68
116
|
force_extraction=force_extraction,
|
|
69
117
|
override_learning_stall=override_learning_stall,
|
|
70
118
|
skip_aggregation=skip_aggregation,
|
|
71
119
|
)
|
|
72
|
-
if
|
|
73
|
-
|
|
120
|
+
if result:
|
|
121
|
+
marker: dict[str, Any] = {"published_up_to": publish_end}
|
|
122
|
+
confirmed_request_id = result.request_id
|
|
123
|
+
if isinstance(confirmed_request_id, str) and confirmed_request_id:
|
|
124
|
+
marker["request_id"] = confirmed_request_id
|
|
125
|
+
state.append(session_id, marker)
|
|
74
126
|
return ("ok", len(interactions))
|
|
75
127
|
return ("failed", len(interactions))
|
|
@@ -20,7 +20,6 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
20
20
|
|
|
21
21
|
_ENV_URL = "REFLEXIO_URL"
|
|
22
22
|
_ENV_API_KEY = "REFLEXIO_API_KEY"
|
|
23
|
-
_DEFAULT_URL = "http://localhost:8071/"
|
|
24
23
|
# Cap every HTTP round-trip from a hook so a hung backend can't stall the
|
|
25
24
|
# Claude Code / Codex session. reflexio's client default is 300s, which is
|
|
26
25
|
# fine for batch workloads but unacceptable on the hook path — a single
|
|
@@ -28,18 +27,48 @@ _DEFAULT_URL = "http://localhost:8071/"
|
|
|
28
27
|
# in ``ids.py`` for short-lived hook HTTP calls.
|
|
29
28
|
_HTTP_TIMEOUT_SECONDS = 5
|
|
30
29
|
_SEARCH_MODE_HYBRID = "hybrid" # reflexio.models.config_schema.SearchMode.HYBRID
|
|
30
|
+
_SOURCE = "claude-smart"
|
|
31
31
|
_UNIFIED_ENTITY_TYPES = ("profiles", "user_playbooks", "agent_playbooks")
|
|
32
32
|
_AGENT_PLAYBOOK_APPROVAL_STATUSES = ("pending", "approved")
|
|
33
33
|
_REJECTED_AGENT_PLAYBOOK_STATUS = "rejected"
|
|
34
|
+
_LOCAL_8071_URLS = {
|
|
35
|
+
"http://localhost:8071",
|
|
36
|
+
"http://localhost:8071/",
|
|
37
|
+
"http://127.0.0.1:8071",
|
|
38
|
+
"http://127.0.0.1:8071/",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _default_url() -> str:
|
|
43
|
+
return f"http://localhost:{os.environ.get('BACKEND_PORT', '8071')}/"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _configured_url() -> str:
|
|
47
|
+
url = os.environ.get(_ENV_URL, "")
|
|
48
|
+
backend_port = os.environ.get("BACKEND_PORT", "")
|
|
49
|
+
if url in _LOCAL_8071_URLS and backend_port not in {"", "8071"}:
|
|
50
|
+
return _default_url()
|
|
51
|
+
return url or _default_url()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class PublishResult:
|
|
56
|
+
"""Outcome and server-confirmed lineage for one publish call."""
|
|
57
|
+
|
|
58
|
+
ok: bool
|
|
59
|
+
request_id: str | None = None
|
|
60
|
+
|
|
61
|
+
def __bool__(self) -> bool:
|
|
62
|
+
return self.ok
|
|
34
63
|
|
|
35
64
|
|
|
36
65
|
@dataclass
|
|
37
66
|
class Adapter:
|
|
38
67
|
"""Wraps the reflexio client and absorbs connection errors.
|
|
39
68
|
|
|
40
|
-
All methods degrade to a neutral no-op
|
|
41
|
-
connection failure so a missing or down reflexio server never
|
|
42
|
-
a
|
|
69
|
+
All methods degrade to a neutral no-op result (an empty list or a falsey
|
|
70
|
+
result) on connection failure so a missing or down reflexio server never
|
|
71
|
+
crashes a host hook.
|
|
43
72
|
"""
|
|
44
73
|
|
|
45
74
|
url: str = ""
|
|
@@ -48,7 +77,7 @@ class Adapter:
|
|
|
48
77
|
|
|
49
78
|
def __post_init__(self) -> None:
|
|
50
79
|
env_config.load_reflexio_env()
|
|
51
|
-
self.url = self.url or
|
|
80
|
+
self.url = self.url or _configured_url()
|
|
52
81
|
self.api_key = self.api_key or os.environ.get(_ENV_API_KEY, "")
|
|
53
82
|
self._client: Any | None = None
|
|
54
83
|
|
|
@@ -92,38 +121,96 @@ class Adapter:
|
|
|
92
121
|
*,
|
|
93
122
|
session_id: str,
|
|
94
123
|
project_id: str,
|
|
124
|
+
request_id: str | None = None,
|
|
95
125
|
interactions: Sequence[dict[str, Any]],
|
|
96
126
|
force_extraction: bool = False,
|
|
97
127
|
override_learning_stall: bool = False,
|
|
98
128
|
skip_aggregation: bool = False,
|
|
99
|
-
) ->
|
|
100
|
-
"""Publish
|
|
129
|
+
) -> PublishResult:
|
|
130
|
+
"""Publish interactions and return this call's confirmed request ID."""
|
|
101
131
|
if not interactions:
|
|
102
|
-
return True
|
|
132
|
+
return PublishResult(True)
|
|
103
133
|
client = self._get_client()
|
|
104
134
|
if client is None:
|
|
105
|
-
return False
|
|
135
|
+
return PublishResult(False)
|
|
106
136
|
try:
|
|
137
|
+
interaction_list = list(interactions)
|
|
138
|
+
raw_request = getattr(client, "_make_request", None)
|
|
139
|
+
supports_source = _supports_keyword(client.publish_interaction, "source")
|
|
140
|
+
if request_id is not None and callable(raw_request):
|
|
141
|
+
payload: dict[str, Any] = {
|
|
142
|
+
"request_id": request_id,
|
|
143
|
+
"user_id": project_id,
|
|
144
|
+
"interaction_data_list": interaction_list,
|
|
145
|
+
"agent_version": runtime.agent_version(),
|
|
146
|
+
"session_id": session_id,
|
|
147
|
+
"skip_aggregation": skip_aggregation,
|
|
148
|
+
"force_extraction": force_extraction,
|
|
149
|
+
"evaluation_only": False,
|
|
150
|
+
"override_learning_stall": override_learning_stall,
|
|
151
|
+
}
|
|
152
|
+
if supports_source:
|
|
153
|
+
payload["source"] = _SOURCE
|
|
154
|
+
try:
|
|
155
|
+
raw_request(
|
|
156
|
+
"POST",
|
|
157
|
+
"/api/publish_interaction",
|
|
158
|
+
json=payload,
|
|
159
|
+
params=None,
|
|
160
|
+
)
|
|
161
|
+
return PublishResult(True, request_id)
|
|
162
|
+
except Exception as exc: # noqa: BLE001
|
|
163
|
+
if not _needs_raw_retrieved_learning_publish(interaction_list):
|
|
164
|
+
raise
|
|
165
|
+
_LOGGER.warning(
|
|
166
|
+
"Could not confirm retrieved-learning links; retrying "
|
|
167
|
+
"the base interaction with the same request ID: %s",
|
|
168
|
+
exc,
|
|
169
|
+
)
|
|
170
|
+
fallback_payload = {
|
|
171
|
+
**payload,
|
|
172
|
+
"interaction_data_list": _without_retrieved_learnings(
|
|
173
|
+
interaction_list
|
|
174
|
+
),
|
|
175
|
+
}
|
|
176
|
+
raw_request(
|
|
177
|
+
"POST",
|
|
178
|
+
"/api/publish_interaction",
|
|
179
|
+
json=fallback_payload,
|
|
180
|
+
params=None,
|
|
181
|
+
)
|
|
182
|
+
return PublishResult(True, request_id)
|
|
183
|
+
if _needs_raw_retrieved_learning_publish(interaction_list):
|
|
184
|
+
_LOGGER.warning(
|
|
185
|
+
"Stable raw publishing is unavailable; publishing "
|
|
186
|
+
"without optional retrieved-learning links"
|
|
187
|
+
)
|
|
188
|
+
interaction_list = _without_retrieved_learnings(interaction_list)
|
|
107
189
|
kwargs = {
|
|
108
190
|
"user_id": project_id,
|
|
109
|
-
"interactions":
|
|
191
|
+
"interactions": interaction_list,
|
|
110
192
|
"agent_version": runtime.agent_version(),
|
|
111
193
|
"session_id": session_id,
|
|
112
194
|
"wait_for_response": False,
|
|
113
195
|
"force_extraction": force_extraction,
|
|
114
196
|
"skip_aggregation": skip_aggregation,
|
|
115
197
|
}
|
|
198
|
+
if supports_source:
|
|
199
|
+
kwargs["source"] = _SOURCE
|
|
116
200
|
if _supports_keyword(client.publish_interaction, "override_learning_stall"):
|
|
117
201
|
kwargs["override_learning_stall"] = override_learning_stall
|
|
118
202
|
elif override_learning_stall:
|
|
119
203
|
_LOGGER.debug(
|
|
120
204
|
"publish_interaction client does not support override_learning_stall"
|
|
121
205
|
)
|
|
122
|
-
client.publish_interaction(**kwargs)
|
|
123
|
-
|
|
206
|
+
response = client.publish_interaction(**kwargs)
|
|
207
|
+
response_request_id = getattr(response, "request_id", None)
|
|
208
|
+
if isinstance(response_request_id, str) and response_request_id:
|
|
209
|
+
return PublishResult(True, response_request_id)
|
|
210
|
+
return PublishResult(True)
|
|
124
211
|
except Exception as exc: # noqa: BLE001
|
|
125
212
|
_LOGGER.warning("publish_interaction failed: %s", exc)
|
|
126
|
-
return False
|
|
213
|
+
return PublishResult(False)
|
|
127
214
|
|
|
128
215
|
def apply_extraction_defaults(self, *, window_size: int, stride_size: int) -> bool:
|
|
129
216
|
"""Push claude-smart's preferred extraction defaults to the reflexio server.
|
|
@@ -340,7 +427,12 @@ class Adapter:
|
|
|
340
427
|
# -----------------------------------------------------------------
|
|
341
428
|
|
|
342
429
|
def search_all(
|
|
343
|
-
self,
|
|
430
|
+
self,
|
|
431
|
+
*,
|
|
432
|
+
project_id: str,
|
|
433
|
+
query: str,
|
|
434
|
+
top_k: int = 5,
|
|
435
|
+
session_id: str | None = None,
|
|
344
436
|
) -> tuple[list[Any], list[Any], list[Any]]:
|
|
345
437
|
"""Unified hybrid search → ``(user_playbooks, agent_playbooks, preferences)``.
|
|
346
438
|
|
|
@@ -354,6 +446,10 @@ class Adapter:
|
|
|
354
446
|
project_id (str): reflexio ``user_id`` for this repo.
|
|
355
447
|
query (str): Free-text query routed through BM25 + vector RRF.
|
|
356
448
|
top_k (int): Cap on results per entity type.
|
|
449
|
+
session_id (str | None): Claude Code session id. When set, the
|
|
450
|
+
server skips results it already returned to this session
|
|
451
|
+
and backfills next-best matches, so repeated hook searches
|
|
452
|
+
within one session stop re-injecting the same rules.
|
|
357
453
|
|
|
358
454
|
Returns:
|
|
359
455
|
tuple[list[Any], list[Any], list[Any]]: ``(user_playbooks,
|
|
@@ -374,6 +470,7 @@ class Adapter:
|
|
|
374
470
|
enable_agent_answer=False,
|
|
375
471
|
top_k=top_k,
|
|
376
472
|
search_mode=_SEARCH_MODE_HYBRID,
|
|
473
|
+
session_id=session_id,
|
|
377
474
|
)
|
|
378
475
|
except Exception as exc: # noqa: BLE001
|
|
379
476
|
self._record_read_error("unified search", exc)
|
|
@@ -449,6 +546,32 @@ def _supports_keyword(callable_obj: Any, keyword: str) -> bool:
|
|
|
449
546
|
return keyword in signature.parameters
|
|
450
547
|
|
|
451
548
|
|
|
549
|
+
def _needs_raw_retrieved_learning_publish(
|
|
550
|
+
interactions: Sequence[dict[str, Any]],
|
|
551
|
+
) -> bool:
|
|
552
|
+
"""Return whether links require a caller-stable raw request.
|
|
553
|
+
|
|
554
|
+
The public client does not accept a caller-supplied request ID, and older
|
|
555
|
+
clients also strip this field. The authenticated helper preserves both.
|
|
556
|
+
"""
|
|
557
|
+
return any(item.get("retrieved_learnings") for item in interactions)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _without_retrieved_learnings(
|
|
561
|
+
interactions: Sequence[dict[str, Any]],
|
|
562
|
+
) -> list[dict[str, Any]]:
|
|
563
|
+
"""Copy interactions without the optional compatibility-only field."""
|
|
564
|
+
|
|
565
|
+
return [
|
|
566
|
+
{
|
|
567
|
+
key: value
|
|
568
|
+
for key, value in interaction.items()
|
|
569
|
+
if key != "retrieved_learnings"
|
|
570
|
+
}
|
|
571
|
+
for interaction in interactions
|
|
572
|
+
]
|
|
573
|
+
|
|
574
|
+
|
|
452
575
|
def _filter_rejected_agent_playbooks(items: list[Any]) -> list[Any]:
|
|
453
576
|
"""Drop rejected shared skills defensively, even if an older backend ignores filters."""
|
|
454
577
|
return [
|
|
@@ -15,27 +15,36 @@ INTERNAL_ENV = "CLAUDE_SMART_INTERNAL"
|
|
|
15
15
|
HOST_CLAUDE_CODE = "claude-code"
|
|
16
16
|
HOST_CODEX = "codex"
|
|
17
17
|
HOST_OPENCODE = "opencode"
|
|
18
|
+
HOST_UNKNOWN = "unknown"
|
|
18
19
|
VALID_HOSTS = frozenset({HOST_CLAUDE_CODE, HOST_CODEX, HOST_OPENCODE})
|
|
19
20
|
|
|
20
21
|
_SHARED_AGENT_VERSION = "claude-code"
|
|
21
22
|
_current_host: str | None = None
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def _resolve_host(value: str | None, fallback: str) -> str:
|
|
26
|
+
return value if value in VALID_HOSTS else fallback
|
|
27
|
+
|
|
28
|
+
|
|
24
29
|
def set_host(value: str | None) -> str:
|
|
25
30
|
"""Set the current host, returning the normalized value."""
|
|
26
31
|
global _current_host
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
_current_host = value if value is not None else HOST_UNKNOWN
|
|
33
|
+
host = _resolve_host(_current_host, HOST_CLAUDE_CODE)
|
|
29
34
|
os.environ[HOST_ENV] = host
|
|
30
35
|
return host
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def host() -> str:
|
|
34
39
|
"""Return the current host, defaulting to Claude Code for compatibility."""
|
|
35
|
-
if _current_host is not None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
value = _current_host if _current_host is not None else os.environ.get(HOST_ENV)
|
|
41
|
+
return _resolve_host(value, HOST_CLAUDE_CODE)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def attribution_host() -> str:
|
|
45
|
+
"""Return the explicitly selected record host, or unknown when unset."""
|
|
46
|
+
value = _current_host if _current_host is not None else os.environ.get(HOST_ENV)
|
|
47
|
+
return _resolve_host(value, HOST_UNKNOWN)
|
|
39
48
|
|
|
40
49
|
|
|
41
50
|
def is_codex() -> bool:
|