claude-smart 0.2.31 → 0.2.33
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/README.md +2 -2
- package/bin/claude-smart.js +222 -20
- 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/api/config/route.ts +11 -2
- package/plugin/dashboard/app/api/health/route.ts +45 -6
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
- package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
- package/plugin/dashboard/app/configure/env/page.tsx +36 -31
- package/plugin/dashboard/app/configure/layout.tsx +1 -1
- package/plugin/dashboard/app/configure/server/page.tsx +8 -14
- package/plugin/dashboard/app/dashboard/page.tsx +311 -115
- package/plugin/dashboard/app/globals.css +80 -66
- package/plugin/dashboard/app/layout.tsx +13 -10
- package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
- package/plugin/dashboard/app/preferences/page.tsx +154 -54
- package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
- package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
- package/plugin/dashboard/app/sessions/page.tsx +14 -10
- package/plugin/dashboard/app/skills/page.tsx +175 -56
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
- package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
- package/plugin/dashboard/components/common/empty-state.tsx +4 -2
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +5 -3
- package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
- package/plugin/dashboard/components/common/stat-card.tsx +9 -5
- package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
- package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
- package/plugin/dashboard/components/ui/input.tsx +1 -0
- package/plugin/dashboard/hooks/use-settings.tsx +30 -61
- package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
- package/plugin/dashboard/lib/config-file.ts +4 -0
- package/plugin/dashboard/lib/reflexio-client.ts +23 -48
- package/plugin/dashboard/lib/session-reader.ts +222 -6
- package/plugin/dashboard/lib/types.ts +21 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +61 -0
- package/plugin/scripts/backend-service.sh +13 -2
- package/plugin/scripts/cli.sh +1 -0
- package/plugin/scripts/codex-hook.js +101 -3
- package/plugin/scripts/dashboard-service.sh +95 -19
- package/plugin/scripts/hook_entry.sh +13 -7
- package/plugin/scripts/smart-install.sh +18 -13
- package/plugin/src/claude_smart/cli.py +138 -24
- package/plugin/src/claude_smart/context_format.py +244 -6
- package/plugin/src/claude_smart/context_inject.py +8 -1
- package/plugin/src/claude_smart/cs_cite.py +186 -34
- package/plugin/src/claude_smart/env_config.py +103 -0
- package/plugin/src/claude_smart/events/stop.py +32 -6
- package/plugin/src/claude_smart/ids.py +32 -0
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +8 -3
- package/plugin/src/claude_smart/reflexio_adapter.py +106 -29
- package/plugin/uv.lock +1 -1
|
@@ -8,16 +8,26 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
+
import inspect
|
|
12
|
+
from collections.abc import Sequence
|
|
11
13
|
from concurrent.futures import ThreadPoolExecutor
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from typing import Any
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any
|
|
14
16
|
|
|
17
|
+
from claude_smart import env_config
|
|
15
18
|
from claude_smart import runtime
|
|
16
19
|
|
|
17
20
|
_LOGGER = logging.getLogger(__name__)
|
|
18
21
|
|
|
19
22
|
_ENV_URL = "REFLEXIO_URL"
|
|
23
|
+
_ENV_API_KEY = "REFLEXIO_API_KEY"
|
|
20
24
|
_DEFAULT_URL = "http://localhost:8071/"
|
|
25
|
+
# Cap every HTTP round-trip from a hook so a hung backend can't stall the
|
|
26
|
+
# Claude Code / Codex session. reflexio's client default is 300s, which is
|
|
27
|
+
# fine for batch workloads but unacceptable on the hook path — a single
|
|
28
|
+
# unhealthy POST would freeze the user's prompt. 5s matches the precedent
|
|
29
|
+
# in ``ids.py`` for short-lived hook HTTP calls.
|
|
30
|
+
_HTTP_TIMEOUT_SECONDS = 5
|
|
21
31
|
_SEARCH_MODE_HYBRID = "hybrid" # reflexio.models.config_schema.SearchMode.HYBRID
|
|
22
32
|
_UNIFIED_ENTITY_TYPES = ("profiles", "user_playbooks", "agent_playbooks")
|
|
23
33
|
_AGENT_PLAYBOOK_APPROVAL_STATUSES = ("pending", "approved")
|
|
@@ -34,9 +44,13 @@ class Adapter:
|
|
|
34
44
|
"""
|
|
35
45
|
|
|
36
46
|
url: str = ""
|
|
47
|
+
api_key: str = ""
|
|
48
|
+
read_errors: list[str] = field(default_factory=list, init=False)
|
|
37
49
|
|
|
38
50
|
def __post_init__(self) -> None:
|
|
51
|
+
env_config.load_reflexio_env()
|
|
39
52
|
self.url = self.url or os.environ.get(_ENV_URL, _DEFAULT_URL)
|
|
53
|
+
self.api_key = self.api_key or os.environ.get(_ENV_API_KEY, "")
|
|
40
54
|
self._client: Any | None = None
|
|
41
55
|
|
|
42
56
|
# -----------------------------------------------------------------
|
|
@@ -53,7 +67,18 @@ class Adapter:
|
|
|
53
67
|
_LOGGER.debug("reflexio not importable: %s", exc)
|
|
54
68
|
return None
|
|
55
69
|
try:
|
|
56
|
-
|
|
70
|
+
try:
|
|
71
|
+
self._client = ReflexioClient(
|
|
72
|
+
url_endpoint=self.url,
|
|
73
|
+
api_key=self.api_key,
|
|
74
|
+
timeout=_HTTP_TIMEOUT_SECONDS,
|
|
75
|
+
)
|
|
76
|
+
except TypeError:
|
|
77
|
+
# Pre-0.2.x reflexio releases didn't accept ``timeout``;
|
|
78
|
+
# fall back so hooks still work against older pinned clients.
|
|
79
|
+
self._client = ReflexioClient(
|
|
80
|
+
url_endpoint=self.url, api_key=self.api_key
|
|
81
|
+
)
|
|
57
82
|
except Exception as exc: # noqa: BLE001 — adapter must never raise.
|
|
58
83
|
_LOGGER.warning("Failed to construct ReflexioClient: %s", exc)
|
|
59
84
|
return None
|
|
@@ -70,6 +95,7 @@ class Adapter:
|
|
|
70
95
|
project_id: str,
|
|
71
96
|
interactions: Sequence[dict[str, Any]],
|
|
72
97
|
force_extraction: bool = False,
|
|
98
|
+
override_learning_stall: bool = False,
|
|
73
99
|
skip_aggregation: bool = False,
|
|
74
100
|
) -> bool:
|
|
75
101
|
"""Publish buffered interactions to reflexio. Returns True on success."""
|
|
@@ -79,15 +105,22 @@ class Adapter:
|
|
|
79
105
|
if client is None:
|
|
80
106
|
return False
|
|
81
107
|
try:
|
|
82
|
-
|
|
83
|
-
user_id
|
|
84
|
-
interactions
|
|
85
|
-
agent_version
|
|
86
|
-
session_id
|
|
87
|
-
wait_for_response
|
|
88
|
-
force_extraction
|
|
89
|
-
skip_aggregation
|
|
90
|
-
|
|
108
|
+
kwargs = {
|
|
109
|
+
"user_id": project_id,
|
|
110
|
+
"interactions": list(interactions),
|
|
111
|
+
"agent_version": runtime.agent_version(),
|
|
112
|
+
"session_id": session_id,
|
|
113
|
+
"wait_for_response": False,
|
|
114
|
+
"force_extraction": force_extraction,
|
|
115
|
+
"skip_aggregation": skip_aggregation,
|
|
116
|
+
}
|
|
117
|
+
if _supports_keyword(client.publish_interaction, "override_learning_stall"):
|
|
118
|
+
kwargs["override_learning_stall"] = override_learning_stall
|
|
119
|
+
elif override_learning_stall:
|
|
120
|
+
_LOGGER.debug(
|
|
121
|
+
"publish_interaction client does not support override_learning_stall"
|
|
122
|
+
)
|
|
123
|
+
client.publish_interaction(**kwargs)
|
|
91
124
|
return True
|
|
92
125
|
except Exception as exc: # noqa: BLE001
|
|
93
126
|
_LOGGER.warning("publish_interaction failed: %s", exc)
|
|
@@ -217,9 +250,10 @@ class Adapter:
|
|
|
217
250
|
def fetch_user_playbooks(self, *, project_id: str, top_k: int = 10) -> list[Any]:
|
|
218
251
|
"""Fetch CURRENT user playbooks for ``project_id``.
|
|
219
252
|
|
|
220
|
-
User playbooks are
|
|
221
|
-
|
|
222
|
-
|
|
253
|
+
User playbooks are scoped by the resolved user id: local installs use
|
|
254
|
+
the project name, while managed installs use ``REFLEXIO_USER_ID``.
|
|
255
|
+
Filtering mirrors the publish path
|
|
256
|
+
(``publish_interaction(user_id=project_id, …)``).
|
|
223
257
|
|
|
224
258
|
Args:
|
|
225
259
|
project_id (str): reflexio ``user_id`` for this repo.
|
|
@@ -232,13 +266,20 @@ class Adapter:
|
|
|
232
266
|
if client is None:
|
|
233
267
|
return []
|
|
234
268
|
try:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
269
|
+
if hasattr(client, "get_user_playbooks"):
|
|
270
|
+
response = client.get_user_playbooks(
|
|
271
|
+
user_id=project_id,
|
|
272
|
+
status_filter=[None], # None => CURRENT in reflexio's filter API
|
|
273
|
+
limit=top_k,
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
response = client.search_user_playbooks(
|
|
277
|
+
user_id=project_id,
|
|
278
|
+
status_filter=[None],
|
|
279
|
+
top_k=top_k,
|
|
280
|
+
)
|
|
240
281
|
except Exception as exc: # noqa: BLE001
|
|
241
|
-
|
|
282
|
+
self._record_read_error("fetch_user_playbooks", exc)
|
|
242
283
|
return []
|
|
243
284
|
return _extract_items(response, "user_playbooks")
|
|
244
285
|
|
|
@@ -260,13 +301,20 @@ class Adapter:
|
|
|
260
301
|
if client is None:
|
|
261
302
|
return []
|
|
262
303
|
try:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
304
|
+
if hasattr(client, "get_agent_playbooks"):
|
|
305
|
+
response = client.get_agent_playbooks(
|
|
306
|
+
agent_version=runtime.agent_version(),
|
|
307
|
+
status_filter=[None],
|
|
308
|
+
limit=top_k,
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
response = client.search_agent_playbooks(
|
|
312
|
+
agent_version=runtime.agent_version(),
|
|
313
|
+
status_filter=[None],
|
|
314
|
+
top_k=top_k,
|
|
315
|
+
)
|
|
268
316
|
except Exception as exc: # noqa: BLE001
|
|
269
|
-
|
|
317
|
+
self._record_read_error("fetch_agent_playbooks", exc)
|
|
270
318
|
return []
|
|
271
319
|
return _filter_rejected_agent_playbooks(
|
|
272
320
|
_extract_items(response, "agent_playbooks")
|
|
@@ -278,13 +326,20 @@ class Adapter:
|
|
|
278
326
|
if client is None:
|
|
279
327
|
return []
|
|
280
328
|
try:
|
|
329
|
+
if hasattr(client, "get_all_profiles"):
|
|
330
|
+
response = client.get_all_profiles(limit=max(top_k * 20, 100))
|
|
331
|
+
return [
|
|
332
|
+
item
|
|
333
|
+
for item in _extract_items(response, "user_profiles")
|
|
334
|
+
if _field(item, "user_id") == project_id
|
|
335
|
+
][:top_k]
|
|
281
336
|
response = client.search_user_profiles(
|
|
282
337
|
user_id=project_id,
|
|
283
338
|
query="",
|
|
284
339
|
top_k=top_k,
|
|
285
340
|
)
|
|
286
341
|
except Exception as exc: # noqa: BLE001
|
|
287
|
-
|
|
342
|
+
self._record_read_error("fetch_project_profiles", exc)
|
|
288
343
|
return []
|
|
289
344
|
return _extract_items(response, "user_profiles")
|
|
290
345
|
|
|
@@ -329,7 +384,7 @@ class Adapter:
|
|
|
329
384
|
search_mode=_SEARCH_MODE_HYBRID,
|
|
330
385
|
)
|
|
331
386
|
except Exception as exc: # noqa: BLE001
|
|
332
|
-
|
|
387
|
+
self._record_read_error("unified search", exc)
|
|
333
388
|
return [], [], []
|
|
334
389
|
return (
|
|
335
390
|
_extract_items(response, "user_playbooks"),
|
|
@@ -374,6 +429,11 @@ class Adapter:
|
|
|
374
429
|
)
|
|
375
430
|
return up_future.result(), ap_future.result(), pr_future.result()
|
|
376
431
|
|
|
432
|
+
def _record_read_error(self, operation: str, exc: Exception) -> None:
|
|
433
|
+
message = f"{operation}: {exc}"
|
|
434
|
+
self.read_errors.append(message)
|
|
435
|
+
_LOGGER.debug("%s failed: %s", operation, exc)
|
|
436
|
+
|
|
377
437
|
|
|
378
438
|
def _extract_items(response: Any, field: str) -> list[Any]:
|
|
379
439
|
"""Pull a list field from a reflexio response object or dict, tolerating shape drift."""
|
|
@@ -386,6 +446,23 @@ def _extract_items(response: Any, field: str) -> list[Any]:
|
|
|
386
446
|
return list(value) if value else []
|
|
387
447
|
|
|
388
448
|
|
|
449
|
+
def _field(item: Any, field: str) -> Any:
|
|
450
|
+
if isinstance(item, dict):
|
|
451
|
+
return item.get(field)
|
|
452
|
+
return getattr(item, field, None)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _supports_keyword(callable_obj: Any, keyword: str) -> bool:
|
|
456
|
+
try:
|
|
457
|
+
signature = inspect.signature(callable_obj)
|
|
458
|
+
except (TypeError, ValueError):
|
|
459
|
+
return False
|
|
460
|
+
for parameter in signature.parameters.values():
|
|
461
|
+
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
|
|
462
|
+
return True
|
|
463
|
+
return keyword in signature.parameters
|
|
464
|
+
|
|
465
|
+
|
|
389
466
|
def _filter_rejected_agent_playbooks(items: list[Any]) -> list[Any]:
|
|
390
467
|
"""Drop rejected shared skills defensively, even if an older backend ignores filters."""
|
|
391
468
|
return [
|