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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Auto-start the claude-smart Next.js dashboard
|
|
3
|
-
#
|
|
2
|
+
# Auto-start the claude-smart Next.js dashboard if it's not already running.
|
|
3
|
+
# Mirrors how claude-mem boots its worker on SessionStart:
|
|
4
4
|
# detached, returns immediately so the hook doesn't block the session.
|
|
5
5
|
#
|
|
6
6
|
# Subcommands:
|
|
7
|
-
# start probe the port; spawn `
|
|
8
|
-
#
|
|
7
|
+
# start probe the port; spawn local `next start` if no claude-smart
|
|
8
|
+
# dashboard is already answering. Never builds in foreground — if
|
|
9
9
|
# .next is missing, logs and bails (Setup is responsible for
|
|
10
10
|
# the build; rerun it or restart Claude Code to retry).
|
|
11
11
|
# stop kill the recorded process group, and (if our dashboard
|
|
@@ -28,7 +28,10 @@ fi
|
|
|
28
28
|
claude_smart_prepend_node_bins
|
|
29
29
|
claude_smart_source_reflexio_env
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
BACKEND_PORT="${BACKEND_PORT:-8071}"
|
|
32
|
+
PORT="${DASHBOARD_PORT:-${PORT:-3001}}"
|
|
33
|
+
export BACKEND_PORT
|
|
34
|
+
claude_smart_derive_reflexio_url_from_backend_port
|
|
32
35
|
|
|
33
36
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
34
37
|
claude_smart_reexec_stable_plugin_root_if_needed "$PLUGIN_ROOT" "dashboard-service.sh" "$@"
|
|
@@ -48,116 +51,46 @@ kill_group() {
|
|
|
48
51
|
claude_smart_kill_tree "$1"
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
dashboard_pid_command_matches() {
|
|
55
|
+
pid="$1"
|
|
56
|
+
cmdline="$(claude_smart_pid_command "$pid" | tr '\n' ' ' || true)"
|
|
57
|
+
[ -n "$cmdline" ] || return 1
|
|
58
|
+
case "$cmdline" in
|
|
59
|
+
*"$DASHBOARD_DIR"*|*next-server*|*next*start*|*node*next*|*claude-smart*dashboard*) return 0 ;;
|
|
60
|
+
*) return 1 ;;
|
|
61
|
+
esac
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
kill_recorded_dashboard() {
|
|
65
|
+
if [ -f "$PID_FILE" ]; then
|
|
66
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
67
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && dashboard_pid_command_matches "$pid"; then
|
|
68
|
+
kill_group "$pid"
|
|
69
|
+
fi
|
|
70
|
+
rm -f "$PID_FILE"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
# True if the marker header served by app/api/health is present on the
|
|
52
75
|
# port. Requires curl — absence is reported as false. This deliberately
|
|
53
76
|
# accepts any claude-smart dashboard, including stale cache versions, so
|
|
54
77
|
# stop/uninstall can safely reap them without touching foreign listeners.
|
|
55
78
|
marker_responds() {
|
|
56
79
|
command -v curl >/dev/null 2>&1 || return 1
|
|
57
|
-
curl -sfI "http://127.0.0.1:$PORT/api/health" 2>/dev/null \
|
|
80
|
+
curl -sfI --connect-timeout 1 --max-time 2 "http://127.0.0.1:$PORT/api/health" 2>/dev/null \
|
|
58
81
|
| grep -qi '^x-claude-smart-dashboard:'
|
|
59
82
|
}
|
|
60
83
|
|
|
61
|
-
dashboard_health_headers() {
|
|
62
|
-
command -v curl >/dev/null 2>&1 || return 1
|
|
63
|
-
curl -sfI --connect-timeout 2 --max-time 5 "http://127.0.0.1:$PORT/api/health" 2>/dev/null
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
header_value_from() {
|
|
67
|
-
header_name="$1"
|
|
68
|
-
awk -v wanted="$header_name" '
|
|
69
|
-
BEGIN { wanted = tolower(wanted) ":" }
|
|
70
|
-
{
|
|
71
|
-
line = $0
|
|
72
|
-
sub(/\r$/, "", line)
|
|
73
|
-
lower = tolower(line)
|
|
74
|
-
if (index(lower, wanted) == 1) {
|
|
75
|
-
sub(/^[^:]*:[[:space:]]*/, "", line)
|
|
76
|
-
print line
|
|
77
|
-
exit
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
canonical_dir() {
|
|
84
|
-
dir="$1"
|
|
85
|
-
(cd "$dir" 2>/dev/null && pwd -P) || printf '%s\n' "$dir"
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
normalize_identity_path() {
|
|
89
|
-
path="$1"
|
|
90
|
-
if claude_smart_is_windows; then
|
|
91
|
-
if command -v cygpath >/dev/null 2>&1; then
|
|
92
|
-
path="$(cygpath -u "$path" 2>/dev/null || printf '%s\n' "$path")"
|
|
93
|
-
else
|
|
94
|
-
path="$(
|
|
95
|
-
printf '%s\n' "$path" | awk '{
|
|
96
|
-
gsub(/\\/, "/")
|
|
97
|
-
if ($0 ~ /^[A-Za-z]:/) {
|
|
98
|
-
drive = tolower(substr($0, 1, 1))
|
|
99
|
-
sub(/^[A-Za-z]:/, "/" drive)
|
|
100
|
-
}
|
|
101
|
-
print
|
|
102
|
-
}'
|
|
103
|
-
)"
|
|
104
|
-
fi
|
|
105
|
-
fi
|
|
106
|
-
while [ "${path%/}" != "$path" ] && [ "$path" != "/" ]; do
|
|
107
|
-
path="${path%/}"
|
|
108
|
-
done
|
|
109
|
-
printf '%s\n' "$path"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# True only if *this plugin root's* dashboard is on the port. The generic
|
|
113
|
-
# x-claude-smart-dashboard marker is intentionally not enough: after an
|
|
114
|
-
# install/update, an older cache can keep serving port 3001 until restarted.
|
|
115
|
-
dashboard_matches_current_root() {
|
|
116
|
-
expected_root="$(normalize_identity_path "$(canonical_dir "$PLUGIN_ROOT")")"
|
|
117
|
-
headers="$(dashboard_health_headers)" || return 1
|
|
118
|
-
printf '%s\n' "$headers" | grep -qi '^x-claude-smart-dashboard:' || return 1
|
|
119
|
-
actual_root="$(printf '%s\n' "$headers" | header_value_from "x-claude-smart-plugin-root")"
|
|
120
|
-
[ -n "$actual_root" ] || return 1
|
|
121
|
-
actual_root="$(normalize_identity_path "$actual_root")"
|
|
122
|
-
[ "$actual_root" = "$expected_root" ]
|
|
123
|
-
}
|
|
124
|
-
|
|
125
84
|
# Kill a claude-smart dashboard listener currently holding the port. Gated by
|
|
126
|
-
# marker_responds so a foreign
|
|
85
|
+
# marker_responds so a foreign listener on the dashboard port is never killed.
|
|
127
86
|
stop_dashboard_listener() {
|
|
128
87
|
marker_responds || return 0
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
sleep 0.2
|
|
136
|
-
done
|
|
137
|
-
kill -KILL "$port_pid" 2>/dev/null || true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
# True only if *our* dashboard is on the port. Uses plugin-root identity so a
|
|
141
|
-
# stale claude-smart listener doesn't cause us to silently skip starting.
|
|
142
|
-
is_our_dashboard_running() {
|
|
143
|
-
if [ -f "$PID_FILE" ]; then
|
|
144
|
-
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
145
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
146
|
-
# PID alive — still verify the port responds with our marker so we
|
|
147
|
-
# don't claim "running" when the server crashed but the group leader
|
|
148
|
-
# lingered.
|
|
149
|
-
if command -v curl >/dev/null 2>&1; then
|
|
150
|
-
dashboard_matches_current_root && return 0
|
|
151
|
-
else
|
|
152
|
-
# No curl — fall back to PID liveness alone.
|
|
153
|
-
return 0
|
|
154
|
-
fi
|
|
155
|
-
fi
|
|
156
|
-
fi
|
|
157
|
-
# No PID or dead PID — probe the port for our marker (recovers after a
|
|
158
|
-
# stale PID file from a crash).
|
|
159
|
-
dashboard_matches_current_root && return 0
|
|
160
|
-
return 1
|
|
88
|
+
kill_recorded_dashboard
|
|
89
|
+
claude_smart_reap_port_listeners_matching "$PORT" \
|
|
90
|
+
'*next-server*' \
|
|
91
|
+
'*next*start*' \
|
|
92
|
+
'*node*next*' \
|
|
93
|
+
'*claude-smart*dashboard*' || true
|
|
161
94
|
}
|
|
162
95
|
|
|
163
96
|
# True if *something* is listening on the port, regardless of marker.
|
|
@@ -170,6 +103,46 @@ port_occupied() {
|
|
|
170
103
|
(echo >"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null
|
|
171
104
|
}
|
|
172
105
|
|
|
106
|
+
resolve_next_bin() {
|
|
107
|
+
if claude_smart_is_windows; then
|
|
108
|
+
for candidate in "$DASHBOARD_DIR/node_modules/.bin/next.cmd" "$DASHBOARD_DIR/node_modules/.bin/next"; do
|
|
109
|
+
[ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
110
|
+
done
|
|
111
|
+
else
|
|
112
|
+
for candidate in "$DASHBOARD_DIR/node_modules/.bin/next" "$DASHBOARD_DIR/node_modules/.bin/next.cmd"; do
|
|
113
|
+
[ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
114
|
+
done
|
|
115
|
+
fi
|
|
116
|
+
return 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
spawn_dashboard() {
|
|
120
|
+
NEXT_BIN="$(resolve_next_bin || true)"
|
|
121
|
+
if [ -z "$NEXT_BIN" ]; then
|
|
122
|
+
reason="local Next.js binary is missing; run claude-smart install/update to restore dashboard dependencies"
|
|
123
|
+
echo "[claude-smart] dashboard: $reason" >>"$LOG_FILE"
|
|
124
|
+
claude_smart_write_dashboard_unavailable "$reason"
|
|
125
|
+
return 1
|
|
126
|
+
fi
|
|
127
|
+
cd "$DASHBOARD_DIR"
|
|
128
|
+
export CLAUDE_SMART_DASHBOARD_WORKSPACE="$WORKSPACE_CWD"
|
|
129
|
+
# Invoke Next directly so custom DASHBOARD_PORT/PORT values are honored
|
|
130
|
+
# without relying on package.json's fixed start script.
|
|
131
|
+
CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached "$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 >>"$LOG_FILE" 2>&1
|
|
132
|
+
dash_pid=$!
|
|
133
|
+
echo "$dash_pid" > "$PID_FILE"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
wait_for_dashboard_marker() {
|
|
137
|
+
attempts="$1"
|
|
138
|
+
while [ "$attempts" -gt 0 ]; do
|
|
139
|
+
marker_responds && return 0
|
|
140
|
+
attempts=$((attempts - 1))
|
|
141
|
+
sleep 1
|
|
142
|
+
done
|
|
143
|
+
marker_responds
|
|
144
|
+
}
|
|
145
|
+
|
|
173
146
|
case "$CMD" in
|
|
174
147
|
start)
|
|
175
148
|
if claude_smart_is_internal_invocation_env; then
|
|
@@ -181,11 +154,19 @@ case "$CMD" in
|
|
|
181
154
|
emit_ok; exit 0
|
|
182
155
|
fi
|
|
183
156
|
if [ ! -d "$DASHBOARD_DIR" ]; then emit_ok; exit 0; fi
|
|
184
|
-
if
|
|
185
|
-
if
|
|
186
|
-
|
|
187
|
-
|
|
157
|
+
if marker_responds; then claude_smart_clear_dashboard_unavailable; emit_ok; exit 0; fi
|
|
158
|
+
if ! claude_smart_service_lock "dashboard"; then
|
|
159
|
+
for _ in 1 2 3 4 5; do
|
|
160
|
+
if marker_responds; then
|
|
161
|
+
claude_smart_clear_dashboard_unavailable
|
|
162
|
+
emit_ok; exit 0
|
|
163
|
+
fi
|
|
164
|
+
sleep 0.2
|
|
165
|
+
done
|
|
166
|
+
emit_ok; exit 0
|
|
188
167
|
fi
|
|
168
|
+
trap 'claude_smart_service_unlock "dashboard"' EXIT
|
|
169
|
+
if marker_responds; then claude_smart_clear_dashboard_unavailable; emit_ok; exit 0; fi
|
|
189
170
|
if port_occupied; then
|
|
190
171
|
echo "[claude-smart] dashboard: port $PORT held by another process; skipping" >>"$LOG_FILE"
|
|
191
172
|
emit_ok; exit 0
|
|
@@ -221,8 +202,6 @@ case "$CMD" in
|
|
|
221
202
|
emit_ok; exit 0
|
|
222
203
|
fi
|
|
223
204
|
|
|
224
|
-
cd "$DASHBOARD_DIR"
|
|
225
|
-
|
|
226
205
|
# Detach so the hook returns immediately. claude_smart_spawn_detached
|
|
227
206
|
# picks the strongest primitive available:
|
|
228
207
|
# - Linux: setsid (puts child in its own session/group, pid==pgid).
|
|
@@ -230,36 +209,41 @@ case "$CMD" in
|
|
|
230
209
|
# - Windows: nohup alone (no process groups; tree-kill via taskkill).
|
|
231
210
|
# Caller-side `>>file 2>&1` redirection is honoured before the child
|
|
232
211
|
# detaches, so per-OS log paths stay identical.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
212
|
+
if ! spawn_dashboard; then
|
|
213
|
+
emit_ok; exit 0
|
|
214
|
+
fi
|
|
215
|
+
if wait_for_dashboard_marker 5; then
|
|
216
|
+
claude_smart_clear_dashboard_unavailable
|
|
217
|
+
emit_ok; exit 0
|
|
218
|
+
fi
|
|
219
|
+
if ! kill -0 "$dash_pid" 2>/dev/null; then
|
|
220
|
+
sleep 2
|
|
221
|
+
if port_occupied; then
|
|
222
|
+
if marker_responds; then
|
|
223
|
+
claude_smart_clear_dashboard_unavailable
|
|
224
|
+
else
|
|
225
|
+
claude_smart_write_dashboard_unavailable "dashboard process exited and port $PORT is now held by a non-claude-smart listener; see $LOG_FILE"
|
|
226
|
+
fi
|
|
227
|
+
else
|
|
228
|
+
echo "[claude-smart] dashboard: first start exited before readiness; retrying once" >>"$LOG_FILE"
|
|
229
|
+
if spawn_dashboard; then
|
|
230
|
+
if wait_for_dashboard_marker 5; then
|
|
231
|
+
claude_smart_clear_dashboard_unavailable
|
|
232
|
+
else
|
|
233
|
+
claude_smart_write_dashboard_unavailable "dashboard process spawned but did not respond on http://127.0.0.1:$PORT within 5s; see $LOG_FILE"
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
247
236
|
fi
|
|
248
|
-
|
|
249
|
-
done
|
|
250
|
-
if [ "$dashboard_ready" != "1" ]; then
|
|
237
|
+
else
|
|
251
238
|
claude_smart_write_dashboard_unavailable "dashboard process spawned but did not respond on http://127.0.0.1:$PORT within 5s; see $LOG_FILE"
|
|
252
239
|
fi
|
|
253
240
|
emit_ok
|
|
254
241
|
;;
|
|
255
242
|
stop)
|
|
256
|
-
|
|
257
|
-
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
258
|
-
rm -f "$PID_FILE"
|
|
259
|
-
fi
|
|
243
|
+
kill_recorded_dashboard
|
|
260
244
|
# Fallback: if a claude-smart dashboard is still responding on the port (e.g.,
|
|
261
245
|
# was started outside this script, or the PGID kill missed because
|
|
262
|
-
# the process wasn't the group leader)
|
|
246
|
+
# the process wasn't the group leader) reap only dashboard-shaped listeners.
|
|
263
247
|
# Gated on the marker header so we never touch a foreign listener.
|
|
264
248
|
stop_dashboard_listener
|
|
265
249
|
emit_ok
|
|
@@ -269,15 +253,12 @@ case "$CMD" in
|
|
|
269
253
|
# interactions/playbooks between sessions. Opt in to teardown by setting
|
|
270
254
|
# CLAUDE_SMART_DASHBOARD_STOP_ON_END=1 in the environment.
|
|
271
255
|
if [ "${CLAUDE_SMART_DASHBOARD_STOP_ON_END:-0}" = "1" ]; then
|
|
272
|
-
|
|
273
|
-
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
274
|
-
rm -f "$PID_FILE"
|
|
275
|
-
fi
|
|
256
|
+
kill_recorded_dashboard
|
|
276
257
|
fi
|
|
277
258
|
emit_ok
|
|
278
259
|
;;
|
|
279
260
|
status)
|
|
280
|
-
if
|
|
261
|
+
if marker_responds; then echo "running on http://localhost:$PORT"; else echo "not running"; fi
|
|
281
262
|
;;
|
|
282
263
|
*)
|
|
283
264
|
emit_ok
|
|
@@ -44,10 +44,13 @@ claude_smart_source_login_path
|
|
|
44
44
|
# Explicit fallback for the astral.sh installer's default paths, in case
|
|
45
45
|
# the user's login-shell rc hasn't yet been re-sourced to pick them up.
|
|
46
46
|
claude_smart_prepend_astral_bins
|
|
47
|
+
BACKEND_PORT="${BACKEND_PORT:-8071}"
|
|
48
|
+
export BACKEND_PORT
|
|
47
49
|
claude_smart_source_reflexio_env
|
|
48
50
|
|
|
49
51
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
50
52
|
claude_smart_reexec_stable_plugin_root_if_needed "$PLUGIN_ROOT" "hook_entry.sh" "$@"
|
|
53
|
+
claude_smart_derive_reflexio_url_from_backend_port
|
|
51
54
|
|
|
52
55
|
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
|
|
53
56
|
STATE_DIR="$HOME/.claude-smart"
|
|
@@ -149,7 +149,21 @@ install_vendored_reflexio() {
|
|
|
149
149
|
local VENDORED_REFLEXIO PLUGIN_PYTHON
|
|
150
150
|
|
|
151
151
|
VENDORED_REFLEXIO="$PLUGIN_ROOT/vendor/reflexio"
|
|
152
|
-
[ -f "$VENDORED_REFLEXIO/pyproject.toml" ]
|
|
152
|
+
if [ ! -f "$VENDORED_REFLEXIO/pyproject.toml" ]; then
|
|
153
|
+
# A host plugin cache (~/.claude/plugins/cache/..., ~/.codex/plugins/cache/...)
|
|
154
|
+
# is only ever populated from the npm package, which always carries the
|
|
155
|
+
# vendored runtime. Missing it there means the plugin was installed straight
|
|
156
|
+
# from the GitHub marketplace, where the bundle is gitignored — the backend
|
|
157
|
+
# could never start. Fail here, at Setup, rather than at first session.
|
|
158
|
+
case "$PLUGIN_ROOT" in
|
|
159
|
+
*/plugins/cache/*)
|
|
160
|
+
write_failure "bundled Reflexio runtime is missing from $VENDORED_REFLEXIO. This happens when claude-smart is installed from the GitHub marketplace, which does not carry the generated runtime. Install with: npx claude-smart install"
|
|
161
|
+
;;
|
|
162
|
+
esac
|
|
163
|
+
# A source checkout legitimately has no vendor bundle (it is generated at
|
|
164
|
+
# pack time); reflexio comes from the plugin venv instead.
|
|
165
|
+
return 0
|
|
166
|
+
fi
|
|
153
167
|
|
|
154
168
|
if claude_smart_is_windows; then
|
|
155
169
|
PLUGIN_PYTHON="$PLUGIN_ROOT/.venv/Scripts/python.exe"
|
|
@@ -2185,6 +2185,20 @@ def cmd_restart(args: argparse.Namespace) -> int:
|
|
|
2185
2185
|
return 0
|
|
2186
2186
|
|
|
2187
2187
|
if do_backend:
|
|
2188
|
+
# Preparing the replacement can reinstall the vendored Reflexio if its
|
|
2189
|
+
# version drifted, so this both validates and repairs. It must succeed
|
|
2190
|
+
# before we stop anything: a backend we cannot replace is better left
|
|
2191
|
+
# running.
|
|
2192
|
+
sys.stdout.write("Preparing reflexio backend replacement…\n")
|
|
2193
|
+
preflight_rc = _run_service(_BACKEND_SCRIPT, "preflight")
|
|
2194
|
+
if preflight_rc != 0:
|
|
2195
|
+
sys.stderr.write(
|
|
2196
|
+
"error: reflexio backend replacement failed preflight; "
|
|
2197
|
+
"existing services were not changed.\n"
|
|
2198
|
+
"Repair the bundled runtime with: npx claude-smart update\n"
|
|
2199
|
+
)
|
|
2200
|
+
return preflight_rc
|
|
2201
|
+
|
|
2188
2202
|
sys.stdout.write("Stopping reflexio backend…\n")
|
|
2189
2203
|
_run_service(_BACKEND_SCRIPT, "stop")
|
|
2190
2204
|
if do_dashboard:
|
|
@@ -59,6 +59,9 @@ def emit_context(
|
|
|
59
59
|
project_id=project_id,
|
|
60
60
|
query=query,
|
|
61
61
|
top_k=top_k,
|
|
62
|
+
# Scopes server-side dedup: rules already injected into this session
|
|
63
|
+
# are not returned again; next-best matches backfill instead.
|
|
64
|
+
session_id=session_id or None,
|
|
62
65
|
)
|
|
63
66
|
renderer = (
|
|
64
67
|
context_format.render_inline_compact_with_registry
|
|
@@ -29,7 +29,10 @@ DEFAULT_CLAUDE_SMART_HOST = runtime.HOST_CLAUDE_CODE
|
|
|
29
29
|
|
|
30
30
|
_LOCAL_DEFAULT_ENTRIES = (
|
|
31
31
|
(
|
|
32
|
-
"#
|
|
32
|
+
"# Set to 1 to route reflexio generation through the active host CLI "
|
|
33
|
+
"(claude for Claude Code, the host compatibility bridge for OpenCode/Codex). "
|
|
34
|
+
"Set to 0 to disable that provider so reflexio auto-detects a cloud LLM "
|
|
35
|
+
"key (anthropic, minimax, openai, etc.) from ~/.claude-smart/.env instead.",
|
|
33
36
|
CLAUDE_SMART_USE_LOCAL_CLI_ENV,
|
|
34
37
|
"1",
|
|
35
38
|
),
|
|
@@ -6,7 +6,7 @@ import re
|
|
|
6
6
|
import time
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from claude_smart import state
|
|
9
|
+
from claude_smart import runtime, state
|
|
10
10
|
|
|
11
11
|
# Tool inputs are persisted locally and later published to reflexio, so we
|
|
12
12
|
# apply a conservative redaction pass at ingestion time. Chosen to avoid
|
|
@@ -144,5 +144,6 @@ def handle(payload: dict[str, Any]) -> None:
|
|
|
144
144
|
"tool_input": _redact(payload.get("tool_input") or {}),
|
|
145
145
|
"tool_output": _redact_string(output_text) if output_text else "",
|
|
146
146
|
"status": _derive_status(tool_response),
|
|
147
|
+
"host": runtime.attribution_host(),
|
|
147
148
|
}
|
|
148
149
|
state.append(session_id, record)
|
|
@@ -21,7 +21,7 @@ import time
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
from claude_smart import env_config, ids, publish, state
|
|
24
|
+
from claude_smart import env_config, ids, publish, runtime, state
|
|
25
25
|
from claude_smart.events.stop import (
|
|
26
26
|
_read_transcript_entries,
|
|
27
27
|
_scan_transcript_for_assistant_text,
|
|
@@ -119,6 +119,7 @@ def _maybe_synthesize_assistant_anchor(
|
|
|
119
119
|
"content": assistant_text,
|
|
120
120
|
"user_id": project_id,
|
|
121
121
|
"synthesised_by": "session_end_anchor",
|
|
122
|
+
"host": runtime.attribution_host(),
|
|
122
123
|
},
|
|
123
124
|
)
|
|
124
125
|
_LOGGER.info(
|
|
@@ -408,6 +408,7 @@ def handle(payload: dict[str, Any]) -> tuple[publish.PublishStatus, int] | None:
|
|
|
408
408
|
"role": "User",
|
|
409
409
|
"content": decision_text,
|
|
410
410
|
"user_id": project_id,
|
|
411
|
+
"host": runtime.attribution_host(),
|
|
411
412
|
},
|
|
412
413
|
)
|
|
413
414
|
elif prompt and not _has_unpublished_user_turn(session_id):
|
|
@@ -418,6 +419,7 @@ def handle(payload: dict[str, Any]) -> tuple[publish.PublishStatus, int] | None:
|
|
|
418
419
|
"role": "User",
|
|
419
420
|
"content": prompt,
|
|
420
421
|
"user_id": project_id,
|
|
422
|
+
"host": runtime.attribution_host(),
|
|
421
423
|
},
|
|
422
424
|
)
|
|
423
425
|
|
|
@@ -426,6 +428,7 @@ def handle(payload: dict[str, Any]) -> tuple[publish.PublishStatus, int] | None:
|
|
|
426
428
|
"role": "Assistant",
|
|
427
429
|
"content": assistant_text,
|
|
428
430
|
"user_id": project_id,
|
|
431
|
+
"host": runtime.attribution_host(),
|
|
429
432
|
}
|
|
430
433
|
if cited_items:
|
|
431
434
|
record["cited_items"] = cited_items
|
|
@@ -28,7 +28,7 @@ import logging
|
|
|
28
28
|
import time
|
|
29
29
|
from typing import Any
|
|
30
30
|
|
|
31
|
-
from claude_smart import context_inject, ids, state
|
|
31
|
+
from claude_smart import context_inject, ids, runtime, state
|
|
32
32
|
|
|
33
33
|
_LOGGER = logging.getLogger(__name__)
|
|
34
34
|
_TOP_K = 3
|
|
@@ -58,6 +58,7 @@ def handle(payload: dict[str, Any]) -> None:
|
|
|
58
58
|
"role": "User",
|
|
59
59
|
"content": prompt,
|
|
60
60
|
"user_id": project_id,
|
|
61
|
+
"host": runtime.attribution_host(),
|
|
61
62
|
},
|
|
62
63
|
)
|
|
63
64
|
|
|
@@ -20,7 +20,8 @@ Two distinct sources of unwanted hook fires:
|
|
|
20
20
|
|
|
21
21
|
Detection signals, OR'd:
|
|
22
22
|
- ``CLAUDE_CODE_ENTRYPOINT`` is anything other than an interactive entrypoint
|
|
23
|
-
(``"cli"
|
|
23
|
+
(``"cli"``, Claude Desktop's ``"claude-desktop"``, or Claude Code IDE
|
|
24
|
+
entrypoints such as ``"claude-vscode"``/``"claude-jetbrains"``). Headless
|
|
24
25
|
``claude -p`` sets ``sdk-cli`` (and the SDKs may set other values). This
|
|
25
26
|
catches case (2) for any third-party tool, not just claude-mem.
|
|
26
27
|
- Env var ``CLAUDE_SMART_INTERNAL=1``, set by reflexio's provider
|
|
@@ -46,7 +47,9 @@ from typing import Any
|
|
|
46
47
|
from claude_smart import runtime
|
|
47
48
|
|
|
48
49
|
_ENTRYPOINT_VAR = "CLAUDE_CODE_ENTRYPOINT"
|
|
49
|
-
_INTERACTIVE_ENTRYPOINTS = frozenset(
|
|
50
|
+
_INTERACTIVE_ENTRYPOINTS = frozenset(
|
|
51
|
+
{"cli", "claude-desktop", "claude-vscode", "claude-jetbrains"}
|
|
52
|
+
)
|
|
50
53
|
_CODEX_TITLE_PROMPT_PREFIX = (
|
|
51
54
|
"You are a helpful assistant. You will be presented with a user prompt, "
|
|
52
55
|
"and your job is to provide a short title for a task"
|
|
@@ -4,6 +4,13 @@ Reflexio's ``LocalScriptAssistant`` sends one JSON payload on stdin and expects
|
|
|
4
4
|
one JSON object on stdout. This module bridges that protocol to a guarded
|
|
5
5
|
local CLI subprocess so candidate playbooks can be evaluated against the active
|
|
6
6
|
host without re-entering claude-smart/reflexio hooks.
|
|
7
|
+
|
|
8
|
+
Host CLI resolution: when ``CLAUDE_SMART_CLI_PATH`` is set in the environment
|
|
9
|
+
(``backend-service.sh`` writes it for ``opencode`` and ``codex`` hosts so
|
|
10
|
+
generation routes through the host-specific compatibility bridge instead of
|
|
11
|
+
the real Claude CLI), this script honours that override. Falling back to
|
|
12
|
+
``shutil.which("claude")`` preserves the legacy Claude Code host behaviour
|
|
13
|
+
where no override is configured.
|
|
7
14
|
"""
|
|
8
15
|
|
|
9
16
|
from __future__ import annotations
|
|
@@ -22,6 +29,20 @@ from claude_smart import internal_call, runtime
|
|
|
22
29
|
_CLI_TIMEOUT_SECONDS = 300
|
|
23
30
|
_READ_ONLY_TOOLS = "Read,Grep,Glob,LS"
|
|
24
31
|
_MUTATING_TOOLS = "Bash,Edit,Write,MultiEdit,NotebookEdit"
|
|
32
|
+
# Mirrors ``reflexio.server.llm.providers.claude_code_provider._ENV_CLI_PATH``
|
|
33
|
+
# so backend-service.sh can route both the reflexio Claude CLI provider and
|
|
34
|
+
# this assistant script through the same host-specific bridge.
|
|
35
|
+
_ENV_CLI_PATH = "CLAUDE_SMART_CLI_PATH"
|
|
36
|
+
_CLAUDE_SMART_COMPAT_BRIDGE_NAMES = frozenset(
|
|
37
|
+
{
|
|
38
|
+
"codex-claude-compat",
|
|
39
|
+
"codex-claude-compat.cmd",
|
|
40
|
+
"codex-claude-compat.js",
|
|
41
|
+
"opencode-claude-compat",
|
|
42
|
+
"opencode-claude-compat.cmd",
|
|
43
|
+
"opencode-claude-compat.js",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
25
46
|
|
|
26
47
|
|
|
27
48
|
class OptimizerAssistantError(Exception):
|
|
@@ -142,26 +163,51 @@ def _run_local_cli(*, prompt: str, system_prompt: str) -> str:
|
|
|
142
163
|
return _run_claude(prompt=prompt, system_prompt=system_prompt)
|
|
143
164
|
|
|
144
165
|
|
|
166
|
+
def _resolve_claude_cli_path() -> str:
|
|
167
|
+
"""Return the host CLI to invoke for evaluation rollouts.
|
|
168
|
+
|
|
169
|
+
When ``CLAUDE_SMART_CLI_PATH`` is set, prefer it so backend-service.sh can
|
|
170
|
+
route the assistant through the same bridge it uses for the reflexio
|
|
171
|
+
``claude-code`` provider (e.g. ``opencode-claude-compat`` for the OpenCode
|
|
172
|
+
host). Falls back to ``shutil.which("claude")`` so Claude Code hosts — and
|
|
173
|
+
any environment that never set the override — continue to use the real
|
|
174
|
+
``claude`` CLI.
|
|
175
|
+
"""
|
|
176
|
+
override = os.environ.get(_ENV_CLI_PATH)
|
|
177
|
+
if override:
|
|
178
|
+
return override
|
|
179
|
+
return shutil.which("claude") or "claude"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _is_claude_smart_compat_bridge(cli_path: str) -> bool:
|
|
183
|
+
return Path(cli_path).name.lower() in _CLAUDE_SMART_COMPAT_BRIDGE_NAMES
|
|
184
|
+
|
|
185
|
+
|
|
145
186
|
def _run_claude(*, prompt: str, system_prompt: str) -> str:
|
|
146
|
-
cli_path =
|
|
147
|
-
# This is an evaluation rollout, not a real user session: allow local
|
|
148
|
-
# inspection, but prevent filesystem, shell, MCP, and session mutations.
|
|
187
|
+
cli_path = _resolve_claude_cli_path()
|
|
149
188
|
cmd = [
|
|
150
189
|
cli_path,
|
|
151
190
|
"-p",
|
|
152
191
|
"--output-format",
|
|
153
192
|
"json",
|
|
154
|
-
"--permission-mode",
|
|
155
|
-
"plan",
|
|
156
|
-
"--tools",
|
|
157
|
-
_READ_ONLY_TOOLS,
|
|
158
|
-
"--disallowedTools",
|
|
159
|
-
_MUTATING_TOOLS,
|
|
160
|
-
"--no-session-persistence",
|
|
161
|
-
"--mcp-config",
|
|
162
|
-
'{"mcpServers": {}}',
|
|
163
|
-
"--strict-mcp-config",
|
|
164
193
|
]
|
|
194
|
+
if not _is_claude_smart_compat_bridge(cli_path):
|
|
195
|
+
# This is an evaluation rollout, not a real user session: allow local
|
|
196
|
+
# inspection, but prevent filesystem, shell, MCP, and session mutations.
|
|
197
|
+
cmd.extend(
|
|
198
|
+
[
|
|
199
|
+
"--permission-mode",
|
|
200
|
+
"plan",
|
|
201
|
+
"--tools",
|
|
202
|
+
_READ_ONLY_TOOLS,
|
|
203
|
+
"--disallowedTools",
|
|
204
|
+
_MUTATING_TOOLS,
|
|
205
|
+
"--no-session-persistence",
|
|
206
|
+
"--mcp-config",
|
|
207
|
+
'{"mcpServers": {}}',
|
|
208
|
+
"--strict-mcp-config",
|
|
209
|
+
]
|
|
210
|
+
)
|
|
165
211
|
if system_prompt:
|
|
166
212
|
cmd.extend(["--append-system-prompt", system_prompt])
|
|
167
213
|
|