claude-smart 0.2.48 → 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 +11 -40
- package/bin/claude-smart.js +393 -55
- 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/opencode/dist/server.mjs +60 -2
- package/plugin/opencode/server.mts +64 -2
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +255 -7
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +766 -123
- package/plugin/scripts/codex-hook.js +79 -229
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +118 -137
- package/plugin/scripts/ensure-plugin-root.sh +106 -8
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/opencode-claude-compat.js +8 -1
- package/plugin/scripts/smart-install.sh +116 -21
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +93 -29
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +56 -11
- 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 +5 -5
- 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/cli/README.md +3 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
- 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 +195 -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 +79 -5
- 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 +63 -6
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
- 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 +199 -2
- 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/interactions.py +63 -2
- 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/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
- 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_scheduler.py +1 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
- 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 +166 -66
- 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 +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
- 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 +414 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
- 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/base/_fts_vec.py +86 -2
- 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 +59 -7
- 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/_source_linkage.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -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 +82 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -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
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# so the SessionStart hook doesn't block the session.
|
|
5
5
|
#
|
|
6
6
|
# Subcommands:
|
|
7
|
+
# preflight verify that this plugin can import its bundled Reflexio
|
|
8
|
+
# runtime without changing any running service.
|
|
7
9
|
# start probe /health; if nothing we recognize is on the port,
|
|
8
10
|
# spawn the prepared venv's `reflexio services start --only
|
|
9
11
|
# backend --no-reload` detached. Polls /health briefly so first
|
|
@@ -27,7 +29,7 @@ fi
|
|
|
27
29
|
claude_smart_prepend_astral_bins
|
|
28
30
|
claude_smart_source_reflexio_env
|
|
29
31
|
|
|
30
|
-
PORT=8071
|
|
32
|
+
PORT="${BACKEND_PORT:-8071}"
|
|
31
33
|
EMBEDDING_PORT="${EMBEDDING_PORT:-8072}"
|
|
32
34
|
# Pass through to `reflexio services start/stop` so the spawned backend
|
|
33
35
|
# binds to PORT instead of reflexio's library default (8081).
|
|
@@ -45,7 +47,7 @@ export REFLEXIO_DEFAULT_ORG_ID="claude-smart"
|
|
|
45
47
|
# Load claude-smart's env from ~/.claude-smart/.env instead of the reflexio
|
|
46
48
|
# default ~/.reflexio/.env, so an OSS reflexio backend's .env on this machine
|
|
47
49
|
# can't leak in. The data dir is independent (LOCAL_STORAGE_PATH), so both still
|
|
48
|
-
# share ~/.reflexio/data. Must stay in sync with env_config.
|
|
50
|
+
# share ~/.reflexio/data. Must stay in sync with env_config.CLAUDE_SMART_ENV_PATH and
|
|
49
51
|
# the source path in _lib.sh (claude_smart_source_reflexio_env).
|
|
50
52
|
export REFLEXIO_ENV_FILE="$HOME/.claude-smart/.env"
|
|
51
53
|
|
|
@@ -63,13 +65,51 @@ fi
|
|
|
63
65
|
# explicitly if we can resolve it from our own (post-login-path) PATH.
|
|
64
66
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
65
67
|
claude_smart_reexec_stable_plugin_root_if_needed "$PLUGIN_ROOT" "backend-service.sh" "$@"
|
|
68
|
+
PLUGIN_ROOT_CANONICAL="$(cd "$PLUGIN_ROOT" 2>/dev/null && pwd -P || printf '%s\n' "$PLUGIN_ROOT")"
|
|
69
|
+
VENDORED_REFLEXIO="$PLUGIN_ROOT_CANONICAL/vendor/reflexio"
|
|
70
|
+
VENDORED_REFLEXIO_FOR_PYTHON="$VENDORED_REFLEXIO"
|
|
71
|
+
if claude_smart_is_windows; then
|
|
72
|
+
VENDORED_REFLEXIO_FOR_PYTHON="$(claude_smart_to_windows_path "$VENDORED_REFLEXIO")"
|
|
73
|
+
fi
|
|
74
|
+
export CLAUDE_SMART_REFLEXIO_VENDOR_ROOT="$VENDORED_REFLEXIO_FOR_PYTHON"
|
|
75
|
+
|
|
76
|
+
plugin_version_from_root() {
|
|
77
|
+
root="$1"
|
|
78
|
+
version="$(
|
|
79
|
+
sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
80
|
+
"$root/.codex-plugin/plugin.json" 2>/dev/null | head -n1
|
|
81
|
+
)"
|
|
82
|
+
if [ -z "$version" ]; then
|
|
83
|
+
version="$(
|
|
84
|
+
sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
85
|
+
"$root/package.json" 2>/dev/null | head -n1
|
|
86
|
+
)"
|
|
87
|
+
fi
|
|
88
|
+
if [ -z "$version" ]; then
|
|
89
|
+
version="$(
|
|
90
|
+
sed -n 's/^[[:space:]]*version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
91
|
+
"$root/pyproject.toml" 2>/dev/null | head -n1
|
|
92
|
+
)"
|
|
93
|
+
fi
|
|
94
|
+
printf '%s\n' "${version:-unknown}"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
plugin_root_has_version_manifest() {
|
|
98
|
+
root="$1"
|
|
99
|
+
[ -f "$root/.codex-plugin/plugin.json" ] || [ -f "$root/package.json" ] || [ -f "$root/pyproject.toml" ]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
PLUGIN_VERSION="$(plugin_version_from_root "$PLUGIN_ROOT_CANONICAL")"
|
|
66
103
|
|
|
67
104
|
if [ -z "${CLAUDE_SMART_CLI_PATH:-}" ]; then
|
|
68
|
-
if [ "${CLAUDE_SMART_HOST:-claude-code}" = "opencode" ]
|
|
105
|
+
if [ "${CLAUDE_SMART_HOST:-claude-code}" = "opencode" ]; then
|
|
69
106
|
# Preserve Reflexio's Claude CLI provider contract while routing
|
|
70
|
-
# generation through the user's authenticated OpenCode setup.
|
|
107
|
+
# generation through the user's authenticated OpenCode setup. The bridge
|
|
108
|
+
# resolves opencode from CLAUDE_SMART_OPENCODE_PATH or PATH; don't require
|
|
109
|
+
# Git Bash's PATH to match the installer shell's PATH here.
|
|
71
110
|
claude_smart_prepend_node_bins
|
|
72
|
-
|
|
111
|
+
CLAUDE_SMART_CLI_PATH="$(claude_smart_opencode_compat_path "$PLUGIN_ROOT")"
|
|
112
|
+
export CLAUDE_SMART_CLI_PATH
|
|
73
113
|
elif [ "${CLAUDE_SMART_HOST:-claude-code}" = "codex" ]; then
|
|
74
114
|
# Reflexio's provider still calls CLAUDE_SMART_CLI_PATH with Claude CLI
|
|
75
115
|
# flags. Use a small compatibility executable that translates that narrow
|
|
@@ -85,7 +125,12 @@ if [ -z "${CLAUDE_SMART_CLI_PATH:-}" ]; then
|
|
|
85
125
|
fi
|
|
86
126
|
|
|
87
127
|
STATE_DIR="$HOME/.claude-smart"
|
|
88
|
-
|
|
128
|
+
backend_pid_key="$(
|
|
129
|
+
printf '%s' "$PLUGIN_ROOT_CANONICAL" | cksum 2>/dev/null | awk '{print $1}' || true
|
|
130
|
+
)"
|
|
131
|
+
backend_pid_key="${backend_pid_key:-default}"
|
|
132
|
+
PID_FILE="$STATE_DIR/backend.$backend_pid_key.pid"
|
|
133
|
+
SHARED_PID_FILE="$STATE_DIR/backend.pid"
|
|
89
134
|
LOG_FILE="$STATE_DIR/backend.log"
|
|
90
135
|
LOG_MAX_BYTES="$(claude_smart_log_max_bytes)"
|
|
91
136
|
mkdir -p "$STATE_DIR"
|
|
@@ -93,14 +138,19 @@ claude_smart_trim_log_file "$LOG_FILE" "$LOG_MAX_BYTES"
|
|
|
93
138
|
|
|
94
139
|
emit_ok() { claude_smart_emit_continue; }
|
|
95
140
|
|
|
141
|
+
# Reason string for a failed bundled-Reflexio preflight. Shared by every site
|
|
142
|
+
# that reports it so the remedy text in emit_start_failure stays in sync.
|
|
143
|
+
VENDOR_PREFLIGHT_FAILURE="bundled Reflexio import preflight failed"
|
|
144
|
+
|
|
96
145
|
emit_start_failure() {
|
|
97
146
|
reason="$1"
|
|
98
147
|
if py=$(claude_smart_resolve_python 2>/dev/null); then
|
|
99
|
-
"$py" - "$reason" <<'PY'
|
|
148
|
+
"$py" - "$reason" "$VENDOR_PREFLIGHT_FAILURE" <<'PY'
|
|
100
149
|
import json
|
|
101
150
|
import sys
|
|
102
151
|
|
|
103
152
|
reason = sys.argv[1].strip()
|
|
153
|
+
vendor_preflight_failure = sys.argv[2]
|
|
104
154
|
message = (
|
|
105
155
|
"> **claude-smart learning backend is not running.** "
|
|
106
156
|
"Interactions are being buffered locally, but learning will not publish "
|
|
@@ -108,10 +158,19 @@ message = (
|
|
|
108
158
|
)
|
|
109
159
|
if reason:
|
|
110
160
|
message += f">\n> Last startup error: `{reason}`\n"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
161
|
+
if reason == vendor_preflight_failure:
|
|
162
|
+
# /claude-smart:restart cannot repair this: the bundled Reflexio runtime
|
|
163
|
+
# only ships in the npm artifact, so the cache has to be replaced.
|
|
164
|
+
message += (
|
|
165
|
+
">\n> The bundled Reflexio runtime is missing or unusable. Repair it "
|
|
166
|
+
"with `npx claude-smart update`, then restart Claude Code."
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
message += (
|
|
170
|
+
">\n> Make sure the local model provider is available: Claude Code needs "
|
|
171
|
+
"`claude`, Codex needs `codex`, and OpenCode needs `opencode`. "
|
|
172
|
+
"Then run `/claude-smart:restart`."
|
|
173
|
+
)
|
|
115
174
|
print(json.dumps({
|
|
116
175
|
"hookSpecificOutput": {
|
|
117
176
|
"hookEventName": "SessionStart",
|
|
@@ -130,79 +189,592 @@ kill_group() {
|
|
|
130
189
|
claude_smart_kill_tree "$1"
|
|
131
190
|
}
|
|
132
191
|
|
|
133
|
-
# True if /health returns 200
|
|
134
|
-
#
|
|
135
|
-
# reflexio on the same port — if you run two reflexio instances on 8071
|
|
136
|
-
# you'll get collision regardless of what we do here.
|
|
192
|
+
# True if /health returns 200, regardless of runtime identity. Used only for
|
|
193
|
+
# generic liveness; ownership checks inspect the listener process.
|
|
137
194
|
backend_healthy() {
|
|
138
195
|
command -v curl >/dev/null 2>&1 || return 1
|
|
139
|
-
curl -sf -o /dev/null "http://127.0.0.1:$PORT/health" 2>/dev/null
|
|
196
|
+
curl -sf --connect-timeout 1 --max-time 1 -o /dev/null "http://127.0.0.1:$PORT/health" 2>/dev/null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
wait_for_health() {
|
|
200
|
+
probe_fn="$1"
|
|
201
|
+
attempts="$2"
|
|
202
|
+
interval="$3"
|
|
203
|
+
while [ "$attempts" -gt 0 ]; do
|
|
204
|
+
"$probe_fn" && return 0
|
|
205
|
+
attempts=$((attempts - 1))
|
|
206
|
+
sleep "$interval"
|
|
207
|
+
done
|
|
208
|
+
"$probe_fn"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
log_embedding_degraded() {
|
|
212
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
213
|
+
"[claude-smart] backend: Embedding service did not become healthy on port $EMBEDDING_PORT; semantic retrieval remains unavailable until the local embedding daemon recovers"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Best-effort port probe. curl catches responsive HTTP listeners; /dev/tcp
|
|
217
|
+
# catches other listeners where bash supports it. Used to avoid stomping on
|
|
218
|
+
# a foreign listener with a failed-to-start uvicorn.
|
|
219
|
+
port_occupied() {
|
|
220
|
+
if command -v curl >/dev/null 2>&1; then
|
|
221
|
+
curl -sf --max-time 2 -o /dev/null "http://127.0.0.1:$PORT" 2>/dev/null && return 0
|
|
222
|
+
fi
|
|
223
|
+
(echo >"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
pid_details() {
|
|
227
|
+
pid="$1"
|
|
228
|
+
seen=" "
|
|
229
|
+
depth=0
|
|
230
|
+
while [ -n "$pid" ] && [ "$pid" != "0" ] && [ "$depth" -lt 6 ]; do
|
|
231
|
+
case "$seen" in *" $pid "*) break ;; esac
|
|
232
|
+
seen="$seen$pid "
|
|
233
|
+
{
|
|
234
|
+
printf 'pid=%s ' "$pid"
|
|
235
|
+
claude_smart_pid_command "$pid" 2>/dev/null || true
|
|
236
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
237
|
+
lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n/cwd=/p' || true
|
|
238
|
+
fi
|
|
239
|
+
}
|
|
240
|
+
parent="$(claude_smart_pid_parent "$pid" 2>/dev/null || true)"
|
|
241
|
+
[ -n "$parent" ] && [ "$parent" != "$pid" ] || break
|
|
242
|
+
pid="$parent"
|
|
243
|
+
depth=$((depth + 1))
|
|
244
|
+
done | tr '\n' ' '
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
marked_backend_ancestor_pid() {
|
|
248
|
+
pid="$1"
|
|
249
|
+
seen=" "
|
|
250
|
+
depth=0
|
|
251
|
+
while [ -n "$pid" ] && [ "$pid" != "0" ] && [ "$depth" -lt 6 ]; do
|
|
252
|
+
case "$seen" in *" $pid "*) break ;; esac
|
|
253
|
+
seen="$seen$pid "
|
|
254
|
+
cmdline="$(claude_smart_pid_command "$pid" | tr '\n' ' ' || true)"
|
|
255
|
+
case "$cmdline" in
|
|
256
|
+
*backend-python-runner.py*--claude-smart-backend=1*)
|
|
257
|
+
printf '%s\n' "$pid"
|
|
258
|
+
return 0
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
parent="$(claude_smart_pid_parent "$pid" 2>/dev/null || true)"
|
|
262
|
+
[ -n "$parent" ] && [ "$parent" != "$pid" ] || break
|
|
263
|
+
pid="$parent"
|
|
264
|
+
depth=$((depth + 1))
|
|
265
|
+
done
|
|
266
|
+
return 1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
stop_backend_pids() {
|
|
270
|
+
pids="$1"
|
|
271
|
+
[ -n "$pids" ] || return 0
|
|
272
|
+
targets=""
|
|
273
|
+
for pid in $pids; do
|
|
274
|
+
target="$(marked_backend_ancestor_pid "$pid" 2>/dev/null || true)"
|
|
275
|
+
for candidate in ${target:-} "$pid"; do
|
|
276
|
+
[ -n "$candidate" ] || continue
|
|
277
|
+
case " $targets " in *" $candidate "*) ;; *) targets="$targets $candidate" ;; esac
|
|
278
|
+
done
|
|
279
|
+
done
|
|
280
|
+
for target in $targets; do
|
|
281
|
+
kill_group "$target"
|
|
282
|
+
done
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
looks_like_claude_smart_backend_pid() {
|
|
286
|
+
pid="$1"
|
|
287
|
+
text="$(pid_details "$pid")"
|
|
288
|
+
[ -n "$text" ] || return 1
|
|
289
|
+
case "$text" in
|
|
290
|
+
*--claude-smart-backend=1*|*--claude-smart-reflexio-vendor-root=*|*--claude-smart-plugin-root=*|*"reflexioai/claude-smart"*|*"reflexioai\\claude-smart"*)
|
|
291
|
+
return 0
|
|
292
|
+
;;
|
|
293
|
+
esac
|
|
294
|
+
for token in $text; do
|
|
295
|
+
value="$(pid_token_path_entry "$token")"
|
|
296
|
+
path_is_or_under_root "$PLUGIN_ROOT_CANONICAL" "$value" && return 0
|
|
297
|
+
done
|
|
298
|
+
return 1
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
is_claude_smart_backend_pid() {
|
|
302
|
+
pid="$1"
|
|
303
|
+
backend_pid_command_matches "$pid" && looks_like_claude_smart_backend_pid "$pid"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
path_is_or_under_root() {
|
|
307
|
+
root="$1"
|
|
308
|
+
value="$2"
|
|
309
|
+
[ -n "$root" ] && [ -n "$value" ] || return 1
|
|
310
|
+
case "$value" in
|
|
311
|
+
"$root"|"$root"/*|"$root"\\*) return 0 ;;
|
|
312
|
+
esac
|
|
313
|
+
return 1
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
pid_token_path_entry() {
|
|
317
|
+
token="$1"
|
|
318
|
+
value="${token#*=}"
|
|
319
|
+
# PYTHONPATH can hold multiple roots; split path-list suffixes without
|
|
320
|
+
# treating a Windows drive prefix like C:\... as a separator.
|
|
321
|
+
case "$value" in
|
|
322
|
+
*";"*) value="${value%%;*}" ;;
|
|
323
|
+
[A-Za-z]:\\*) ;;
|
|
324
|
+
*) value="${value%%:*}" ;;
|
|
325
|
+
esac
|
|
326
|
+
printf '%s\n' "$value"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pid_vendor_root() {
|
|
330
|
+
pid="$1"
|
|
331
|
+
text="$(pid_details "$pid")"
|
|
332
|
+
[ -n "$text" ] || return 1
|
|
333
|
+
for token in $text; do
|
|
334
|
+
case "$token" in
|
|
335
|
+
--claude-smart-reflexio-vendor-root=*)
|
|
336
|
+
printf '%s\n' "${token#--claude-smart-reflexio-vendor-root=}"
|
|
337
|
+
return 0
|
|
338
|
+
;;
|
|
339
|
+
--claude-smart-plugin-root=*)
|
|
340
|
+
printf '%s/vendor/reflexio\n' "${token#--claude-smart-plugin-root=}"
|
|
341
|
+
return 0
|
|
342
|
+
;;
|
|
343
|
+
esac
|
|
344
|
+
value="$(pid_token_path_entry "$token")"
|
|
345
|
+
case "$value" in
|
|
346
|
+
*/vendor/reflexio|*\\vendor\\reflexio)
|
|
347
|
+
root="$(plugin_root_from_vendor_root "$value" 2>/dev/null || true)"
|
|
348
|
+
if [ -n "$root" ] && plugin_root_has_version_manifest "$root"; then
|
|
349
|
+
printf '%s\n' "$value"
|
|
350
|
+
return 0
|
|
351
|
+
fi
|
|
352
|
+
;;
|
|
353
|
+
esac
|
|
354
|
+
done
|
|
355
|
+
return 1
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
plugin_root_from_vendor_root() {
|
|
359
|
+
vendor="$1"
|
|
360
|
+
case "$vendor" in
|
|
361
|
+
*/vendor/reflexio) printf '%s\n' "${vendor%/vendor/reflexio}"; return 0 ;;
|
|
362
|
+
*\\vendor\\reflexio) printf '%s\n' "${vendor%\\vendor\\reflexio}"; return 0 ;;
|
|
363
|
+
esac
|
|
364
|
+
return 1
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
pid_plugin_root() {
|
|
368
|
+
text="$(pid_details "$1")"
|
|
369
|
+
for token in $text; do
|
|
370
|
+
case "$token" in
|
|
371
|
+
--claude-smart-plugin-root=*)
|
|
372
|
+
printf '%s\n' "${token#--claude-smart-plugin-root=}"
|
|
373
|
+
return 0
|
|
374
|
+
;;
|
|
375
|
+
esac
|
|
376
|
+
done
|
|
377
|
+
vendor="$(pid_vendor_root "$1" 2>/dev/null || true)"
|
|
378
|
+
if [ -n "$vendor" ]; then
|
|
379
|
+
plugin_root_from_vendor_root "$vendor" && return 0
|
|
380
|
+
fi
|
|
381
|
+
for token in $text; do
|
|
382
|
+
case "$token" in
|
|
383
|
+
cwd=*)
|
|
384
|
+
root="${token#cwd=}"
|
|
385
|
+
if plugin_root_has_version_manifest "$root"; then
|
|
386
|
+
printf '%s\n' "$root"
|
|
387
|
+
return 0
|
|
388
|
+
fi
|
|
389
|
+
;;
|
|
390
|
+
esac
|
|
391
|
+
done
|
|
392
|
+
return 1
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
pid_plugin_version() {
|
|
396
|
+
text="$(pid_details "$1")"
|
|
397
|
+
for token in $text; do
|
|
398
|
+
case "$token" in
|
|
399
|
+
--claude-smart-version=*)
|
|
400
|
+
printf '%s\n' "${token#--claude-smart-version=}"
|
|
401
|
+
return 0
|
|
402
|
+
;;
|
|
403
|
+
esac
|
|
404
|
+
done
|
|
405
|
+
root="$(pid_plugin_root "$1" 2>/dev/null || true)"
|
|
406
|
+
[ -n "$root" ] || { printf '%s\n' "unknown"; return 0; }
|
|
407
|
+
plugin_version_from_root "$root"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
pid_uses_current_vendor_root() {
|
|
411
|
+
pid="$1"
|
|
412
|
+
text="$(pid_details "$pid")"
|
|
413
|
+
[ -n "$text" ] || return 1
|
|
414
|
+
for token in $text; do
|
|
415
|
+
case "$token" in
|
|
416
|
+
--claude-smart-plugin-root=*)
|
|
417
|
+
value="${token#--claude-smart-plugin-root=}"
|
|
418
|
+
path_is_or_under_root "$PLUGIN_ROOT_CANONICAL" "$value" && return 0
|
|
419
|
+
;;
|
|
420
|
+
--claude-smart-reflexio-vendor-root=*)
|
|
421
|
+
value="${token#--claude-smart-reflexio-vendor-root=}"
|
|
422
|
+
path_is_or_under_root "$VENDORED_REFLEXIO" "$value" && return 0
|
|
423
|
+
path_is_or_under_root "$VENDORED_REFLEXIO_FOR_PYTHON" "$value" && return 0
|
|
424
|
+
;;
|
|
425
|
+
*)
|
|
426
|
+
value="$(pid_token_path_entry "$token")"
|
|
427
|
+
path_is_or_under_root "$VENDORED_REFLEXIO" "$value" && return 0
|
|
428
|
+
path_is_or_under_root "$VENDORED_REFLEXIO_FOR_PYTHON" "$value" && return 0
|
|
429
|
+
;;
|
|
430
|
+
esac
|
|
431
|
+
done
|
|
432
|
+
return 1
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
backend_version_core_parts() {
|
|
436
|
+
version="$1"
|
|
437
|
+
printf '%s\n' "$version" \
|
|
438
|
+
| sed -n 's/^\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\)$/\1 \2 \3/p'
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
backend_version_older_than() {
|
|
442
|
+
actual_version_input="$1"
|
|
443
|
+
target_version_input="$2"
|
|
444
|
+
actual_core="$(backend_version_core_parts "$actual_version_input")"
|
|
445
|
+
target_core="$(backend_version_core_parts "$target_version_input")"
|
|
446
|
+
[ -n "$actual_core" ] && [ -n "$target_core" ] || return 1
|
|
447
|
+
|
|
448
|
+
set -- $actual_core
|
|
449
|
+
actual_major="$1"; actual_minor="$2"; actual_patch="$3"
|
|
450
|
+
set -- $target_core
|
|
451
|
+
target_major="$1"; target_minor="$2"; target_patch="$3"
|
|
452
|
+
|
|
453
|
+
[ "$actual_major" -lt "$target_major" ] && return 0
|
|
454
|
+
[ "$actual_major" -gt "$target_major" ] && return 1
|
|
455
|
+
[ "$actual_minor" -lt "$target_minor" ] && return 0
|
|
456
|
+
[ "$actual_minor" -gt "$target_minor" ] && return 1
|
|
457
|
+
[ "$actual_patch" -lt "$target_patch" ] && return 0
|
|
458
|
+
return 1
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
backend_pid_current_or_compatible() {
|
|
462
|
+
pid="$1"
|
|
463
|
+
pid_uses_current_vendor_root "$pid" && return 0
|
|
464
|
+
[ "$PLUGIN_VERSION" = "unknown" ] && return 0
|
|
465
|
+
running_version="$(pid_plugin_version "$pid")"
|
|
466
|
+
[ "$running_version" = "unknown" ] && return 0
|
|
467
|
+
! backend_version_older_than "$running_version" "$PLUGIN_VERSION"
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
backend_pid_strictly_older_than_plugin() {
|
|
471
|
+
pid="$1"
|
|
472
|
+
[ "$PLUGIN_VERSION" != "unknown" ] || return 1
|
|
473
|
+
pid_uses_current_vendor_root "$pid" && return 1
|
|
474
|
+
running_version="$(pid_plugin_version "$pid")"
|
|
475
|
+
[ "$running_version" != "unknown" ] || return 1
|
|
476
|
+
backend_version_older_than "$running_version" "$PLUGIN_VERSION"
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
backend_listener_pids() {
|
|
480
|
+
claude_smart_port_listener_pids "$PORT"
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
classify_backend_listener() {
|
|
484
|
+
pids="$(backend_listener_pids || true)"
|
|
485
|
+
[ -n "$pids" ] || { printf '%s\n' "free"; return 0; }
|
|
486
|
+
for pid in $pids; do
|
|
487
|
+
if is_claude_smart_backend_pid "$pid"; then
|
|
488
|
+
printf '%s\n' "claude_smart"
|
|
489
|
+
return 0
|
|
490
|
+
fi
|
|
491
|
+
done
|
|
492
|
+
printf '%s\n' "foreign"
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
backend_owned_by_current_vendor() {
|
|
496
|
+
backend_healthy || return 1
|
|
497
|
+
pids="$(backend_listener_pids)"
|
|
498
|
+
for pid in $pids; do
|
|
499
|
+
if is_claude_smart_backend_pid "$pid" && pid_uses_current_vendor_root "$pid"; then
|
|
500
|
+
return 0
|
|
501
|
+
fi
|
|
502
|
+
done
|
|
503
|
+
return 1
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
backend_owned_by_stale_claude_smart() {
|
|
507
|
+
pids="$(backend_listener_pids)"
|
|
508
|
+
for pid in $pids; do
|
|
509
|
+
if is_claude_smart_backend_pid "$pid" && backend_pid_strictly_older_than_plugin "$pid"; then
|
|
510
|
+
return 0
|
|
511
|
+
fi
|
|
512
|
+
done
|
|
513
|
+
return 1
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
backend_current_or_compatible() {
|
|
517
|
+
backend_healthy || return 1
|
|
518
|
+
pids="$(backend_listener_pids)"
|
|
519
|
+
for pid in $pids; do
|
|
520
|
+
if is_claude_smart_backend_pid "$pid" && backend_pid_current_or_compatible "$pid"; then
|
|
521
|
+
return 0
|
|
522
|
+
fi
|
|
523
|
+
done
|
|
524
|
+
return 1
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
embedding_owned_by_current_vendor() {
|
|
528
|
+
[ "${CLAUDE_SMART_USE_LOCAL_EMBEDDING:-}" = "1" ] || return 0
|
|
529
|
+
pids="$(claude_smart_port_listener_pids "$EMBEDDING_PORT")"
|
|
530
|
+
for pid in $pids; do
|
|
531
|
+
if is_claude_smart_backend_pid "$pid" && pid_uses_current_vendor_root "$pid"; then
|
|
532
|
+
return 0
|
|
533
|
+
fi
|
|
534
|
+
done
|
|
535
|
+
return 1
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
embedding_current_or_compatible() {
|
|
539
|
+
[ "${CLAUDE_SMART_USE_LOCAL_EMBEDDING:-}" = "1" ] || return 0
|
|
540
|
+
pids="$(claude_smart_port_listener_pids "$EMBEDDING_PORT")"
|
|
541
|
+
for pid in $pids; do
|
|
542
|
+
if is_claude_smart_backend_pid "$pid" && backend_pid_current_or_compatible "$pid"; then
|
|
543
|
+
return 0
|
|
544
|
+
fi
|
|
545
|
+
done
|
|
546
|
+
return 1
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
verify_bundled_reflexio_import() {
|
|
550
|
+
python_bin="$1"
|
|
551
|
+
pythonpath="$2"
|
|
552
|
+
vendor_root_for_python="$3"
|
|
553
|
+
[ -d "$VENDORED_REFLEXIO/reflexio" ] || {
|
|
554
|
+
echo "bundled Reflexio package not found at $VENDORED_REFLEXIO" >&2
|
|
555
|
+
return 1
|
|
556
|
+
}
|
|
557
|
+
PYTHONPATH="$pythonpath" "$python_bin" - "$vendor_root_for_python" <<'PY'
|
|
558
|
+
from pathlib import Path
|
|
559
|
+
import sys
|
|
560
|
+
|
|
561
|
+
vendor = Path(sys.argv[1]).resolve()
|
|
562
|
+
try:
|
|
563
|
+
import reflexio
|
|
564
|
+
except Exception as exc:
|
|
565
|
+
print(f"failed to import bundled reflexio from {vendor}: {exc}", file=sys.stderr)
|
|
566
|
+
raise SystemExit(1)
|
|
567
|
+
|
|
568
|
+
module = Path(reflexio.__file__).resolve()
|
|
569
|
+
try:
|
|
570
|
+
module.relative_to(vendor)
|
|
571
|
+
except ValueError:
|
|
572
|
+
print(
|
|
573
|
+
f"reflexio import resolved outside bundled vendor: {module} (expected under {vendor})",
|
|
574
|
+
file=sys.stderr,
|
|
575
|
+
)
|
|
576
|
+
raise SystemExit(1)
|
|
577
|
+
PY
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
prepare_backend_import_context() {
|
|
581
|
+
backend_pythonpath="${PYTHONPATH:-}"
|
|
582
|
+
if [ -d "$VENDORED_REFLEXIO/reflexio" ]; then
|
|
583
|
+
vendor_pythonpath="$VENDORED_REFLEXIO"
|
|
584
|
+
pythonpath_sep=":"
|
|
585
|
+
if claude_smart_is_windows; then
|
|
586
|
+
# Native Windows Python expects ;-separated Windows-style paths in
|
|
587
|
+
# PYTHONPATH; MSYS does not auto-convert arbitrary env vars.
|
|
588
|
+
pythonpath_sep=";"
|
|
589
|
+
vendor_pythonpath="$VENDORED_REFLEXIO_FOR_PYTHON"
|
|
590
|
+
fi
|
|
591
|
+
backend_pythonpath="$vendor_pythonpath${backend_pythonpath:+$pythonpath_sep$backend_pythonpath}"
|
|
592
|
+
fi
|
|
593
|
+
backend_python="$(claude_smart_plugin_python "$PLUGIN_ROOT")"
|
|
140
594
|
}
|
|
141
595
|
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
596
|
+
# Repair the vendored install if its version drifted, then prove this plugin
|
|
597
|
+
# can import its own bundled Reflexio. Callers run this before any action that
|
|
598
|
+
# would stop or replace a running service.
|
|
599
|
+
preflight_bundled_reflexio_runtime() {
|
|
600
|
+
ensure_vendored_reflexio_active || return 1
|
|
601
|
+
prepare_backend_import_context
|
|
602
|
+
verify_bundled_reflexio_import "$backend_python" "$backend_pythonpath" "$VENDORED_REFLEXIO_FOR_PYTHON"
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
# True only if the recorded PID is alive, /health responds, and the listener
|
|
606
|
+
# process is using this package's bundled Reflexio import path.
|
|
145
607
|
is_our_backend_running() {
|
|
146
608
|
if [ -f "$PID_FILE" ]; then
|
|
147
609
|
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
148
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
149
|
-
|
|
610
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && backend_pid_command_matches "$pid"; then
|
|
611
|
+
if command -v curl >/dev/null 2>&1; then
|
|
612
|
+
backend_healthy && backend_pid_current_or_compatible "$pid" && return 0
|
|
613
|
+
else
|
|
614
|
+
backend_pid_current_or_compatible "$pid" && return 0
|
|
615
|
+
fi
|
|
150
616
|
fi
|
|
151
617
|
fi
|
|
152
|
-
# Recover from a missing PID file
|
|
153
|
-
#
|
|
154
|
-
|
|
618
|
+
# Recover from a missing PID file only when the listener proves it belongs
|
|
619
|
+
# to a compatible claude-smart package. A generic healthy service is not
|
|
620
|
+
# enough, and a known older claude-smart package is restarted later.
|
|
621
|
+
backend_current_or_compatible && return 0
|
|
155
622
|
return 1
|
|
156
623
|
}
|
|
157
624
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
625
|
+
stop_backend_listener_if_owned() {
|
|
626
|
+
pids="$(backend_listener_pids)"
|
|
627
|
+
[ -n "$pids" ] || return 1
|
|
628
|
+
ours=""
|
|
629
|
+
for pid in $pids; do
|
|
630
|
+
if is_claude_smart_backend_pid "$pid" && { pid_uses_current_vendor_root "$pid" || backend_pid_strictly_older_than_plugin "$pid"; }; then
|
|
631
|
+
ours="$ours $pid"
|
|
632
|
+
fi
|
|
633
|
+
done
|
|
634
|
+
[ -n "$ours" ] || return 1
|
|
635
|
+
stop_backend_pids "$ours"
|
|
162
636
|
}
|
|
163
637
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
shift || true
|
|
172
|
-
[ "$#" -eq 0 ] && return 0
|
|
173
|
-
command -v lsof >/dev/null 2>&1 || return 0
|
|
174
|
-
candidates=$(lsof -ti:"$port" 2>/dev/null) || candidates=""
|
|
175
|
-
[ -z "$candidates" ] && return 0
|
|
176
|
-
ours=""
|
|
177
|
-
for pid in $candidates; do
|
|
178
|
-
cmdline=$(ps -p "$pid" -o command= 2>/dev/null || true)
|
|
179
|
-
for pattern in "$@"; do
|
|
180
|
-
if [[ "$cmdline" == $pattern ]]; then
|
|
181
|
-
ours="$ours $pid"
|
|
182
|
-
break
|
|
183
|
-
fi
|
|
184
|
-
done
|
|
638
|
+
stop_stale_backend_listener_if_owned() {
|
|
639
|
+
backend_owned_by_stale_claude_smart || return 1
|
|
640
|
+
stale=""
|
|
641
|
+
for pid in $(backend_listener_pids); do
|
|
642
|
+
if is_claude_smart_backend_pid "$pid" && backend_pid_strictly_older_than_plugin "$pid"; then
|
|
643
|
+
stale="$stale $pid"
|
|
644
|
+
fi
|
|
185
645
|
done
|
|
186
|
-
[ -
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
646
|
+
[ -n "$stale" ] || return 1
|
|
647
|
+
stop_backend_pids "$stale"
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
stop_stale_embedding_listener_if_owned() {
|
|
651
|
+
[ "${CLAUDE_SMART_USE_LOCAL_EMBEDDING:-}" = "1" ] || return 0
|
|
652
|
+
pids="$(claude_smart_port_listener_pids "$EMBEDDING_PORT")"
|
|
653
|
+
[ -n "$pids" ] || return 0
|
|
654
|
+
ours=""
|
|
655
|
+
for pid in $pids; do
|
|
656
|
+
if is_claude_smart_backend_pid "$pid" && backend_pid_strictly_older_than_plugin "$pid"; then
|
|
657
|
+
ours="$ours $pid"
|
|
658
|
+
fi
|
|
193
659
|
done
|
|
194
|
-
[ -
|
|
195
|
-
|
|
196
|
-
kill -KILL $remaining 2>/dev/null || true
|
|
660
|
+
[ -n "$ours" ] || return 0
|
|
661
|
+
stop_backend_pids "$ours"
|
|
197
662
|
}
|
|
198
663
|
|
|
199
664
|
# Describe what (if anything) is currently listening on $1. Returns
|
|
200
|
-
# "<command> (pid <pid>)" or empty if the port is free or
|
|
201
|
-
#
|
|
665
|
+
# "<command> (pid <pid>)" or empty if the port is free or no platform probe is
|
|
666
|
+
# available. Used to make port-conflict log lines diagnosable.
|
|
202
667
|
port_holder() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
668
|
+
claude_smart_port_holder "$1"
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
recorded_backend_pid() {
|
|
672
|
+
[ -f "$PID_FILE" ] || return 1
|
|
673
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
674
|
+
[ -n "$pid" ] || return 1
|
|
675
|
+
printf '%s\n' "$pid"
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
shared_backend_pid() {
|
|
679
|
+
[ -f "$SHARED_PID_FILE" ] || return 1
|
|
680
|
+
pid=$(cat "$SHARED_PID_FILE" 2>/dev/null || echo "")
|
|
681
|
+
[ -n "$pid" ] || return 1
|
|
682
|
+
printf '%s\n' "$pid"
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
backend_pid_command_matches() {
|
|
686
|
+
pid="$1"
|
|
687
|
+
cmdline="$(claude_smart_pid_command "$pid" | tr '\n' ' ' || true)"
|
|
688
|
+
[ -n "$cmdline" ] || return 1
|
|
689
|
+
case "$cmdline" in
|
|
690
|
+
*backend-python-runner.py*--claude-smart-backend=1*services*start*|*reflexio.cli*services*start*|*reflexio.server*|*uvicorn*reflexio*|*reflexio*uvicorn*) return 0 ;;
|
|
691
|
+
*) return 1 ;;
|
|
692
|
+
esac
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
recorded_backend_pid_alive() {
|
|
696
|
+
pid="$(recorded_backend_pid 2>/dev/null || true)"
|
|
697
|
+
[ -n "$pid" ] || return 1
|
|
698
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
699
|
+
backend_pid_command_matches "$pid"
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
backend_pid_is_own_recorded() {
|
|
703
|
+
pid="$1"
|
|
704
|
+
recorded="$(recorded_backend_pid 2>/dev/null || true)"
|
|
705
|
+
[ -n "$recorded" ] && [ "$pid" = "$recorded" ]
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
pid_cwd_matches_current_plugin_root() {
|
|
709
|
+
pid="$1"
|
|
710
|
+
command -v lsof >/dev/null 2>&1 || return 1
|
|
711
|
+
cwd="$(
|
|
712
|
+
lsof -a -p "$pid" -d cwd -Fn 2>/dev/null \
|
|
713
|
+
| sed -n 's/^n//p' \
|
|
714
|
+
| head -n 1
|
|
715
|
+
)"
|
|
716
|
+
[ -n "$cwd" ] || return 1
|
|
717
|
+
cwd_canonical="$(cd "$cwd" 2>/dev/null && pwd -P || printf '%s\n' "$cwd")"
|
|
718
|
+
[ "$cwd_canonical" = "$PLUGIN_ROOT_CANONICAL" ]
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
backend_pid_is_legacy_same_root_shared() {
|
|
722
|
+
pid="$1"
|
|
723
|
+
shared="$(shared_backend_pid 2>/dev/null || true)"
|
|
724
|
+
[ -n "$shared" ] && [ "$pid" = "$shared" ] || return 1
|
|
725
|
+
pid_cwd_matches_current_plugin_root "$pid"
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
backend_start_grace_seconds() {
|
|
729
|
+
value="${CLAUDE_SMART_BACKEND_START_GRACE_SECONDS:-60}"
|
|
730
|
+
case "$value" in
|
|
731
|
+
''|*[!0-9]*) printf '%s\n' "60" ;;
|
|
732
|
+
*) printf '%s\n' "$value" ;;
|
|
733
|
+
esac
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
recorded_backend_pid_in_startup_grace() {
|
|
737
|
+
recorded_backend_pid_alive || return 1
|
|
738
|
+
now="$(claude_smart_now_epoch 2>/dev/null || printf '%s\n' "0")"
|
|
739
|
+
mtime="$(claude_smart_path_mtime_epoch "$PID_FILE" 2>/dev/null || printf '%s\n' "$now")"
|
|
740
|
+
age=$((now - mtime))
|
|
741
|
+
[ "$age" -lt "$(backend_start_grace_seconds)" ]
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
kill_pid_if_directionally_owned() {
|
|
745
|
+
pid="$1"
|
|
746
|
+
[ -n "$pid" ] || return 1
|
|
747
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
748
|
+
is_claude_smart_backend_pid "$pid" || return 1
|
|
749
|
+
if pid_uses_current_vendor_root "$pid" \
|
|
750
|
+
|| backend_pid_strictly_older_than_plugin "$pid" \
|
|
751
|
+
|| backend_pid_is_own_recorded "$pid" \
|
|
752
|
+
|| backend_pid_is_legacy_same_root_shared "$pid"; then
|
|
753
|
+
kill_group "$pid"
|
|
754
|
+
return 0
|
|
755
|
+
fi
|
|
756
|
+
return 1
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
kill_recorded_backend() {
|
|
760
|
+
pid="$(recorded_backend_pid 2>/dev/null || true)"
|
|
761
|
+
if [ -n "$pid" ]; then
|
|
762
|
+
if ! kill_pid_if_directionally_owned "$pid"; then
|
|
763
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
764
|
+
"[claude-smart] backend: recorded pid $pid is not owned by this plugin; removing stale root-specific pid file"
|
|
765
|
+
fi
|
|
766
|
+
rm -f "$PID_FILE"
|
|
767
|
+
fi
|
|
768
|
+
shared_pid="$(shared_backend_pid 2>/dev/null || true)"
|
|
769
|
+
if [ -n "$shared_pid" ]; then
|
|
770
|
+
if kill_pid_if_directionally_owned "$shared_pid"; then
|
|
771
|
+
rm -f "$SHARED_PID_FILE"
|
|
772
|
+
else
|
|
773
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
774
|
+
"[claude-smart] backend: shared pid $shared_pid is not owned by this plugin; leaving shared pid file"
|
|
775
|
+
fi
|
|
776
|
+
fi
|
|
777
|
+
return 0
|
|
206
778
|
}
|
|
207
779
|
|
|
208
780
|
ensure_vendored_reflexio_active() {
|
|
@@ -234,27 +806,31 @@ PY
|
|
|
234
806
|
uv pip install --project "$PLUGIN_ROOT" --python "$plugin_python" --quiet --reinstall --no-deps "$vendor" >&2
|
|
235
807
|
}
|
|
236
808
|
|
|
237
|
-
# Full shutdown: kill the recorded process group (if any) then sweep
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# path so a stale/missing PID file doesn't produce a silent no-op, and
|
|
241
|
-
# so a stale embedding service on EMBEDDING_PORT doesn't block the next
|
|
242
|
-
# fresh boot (e.g. when codex-hook.js falls back to 8072 for the main
|
|
243
|
-
# backend because 8071 is held by another app).
|
|
809
|
+
# Full shutdown: kill the recorded process group (if any) then sweep stale
|
|
810
|
+
# claude-smart-owned backend/embedding listeners. Foreign services on the same
|
|
811
|
+
# ports are left alone.
|
|
244
812
|
full_stop() {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
rm -f "$PID_FILE"
|
|
248
|
-
fi
|
|
249
|
-
reap_port_listeners "$PORT" '*reflexio*' '*uvicorn*'
|
|
813
|
+
kill_recorded_backend
|
|
814
|
+
stop_backend_listener_if_owned || true
|
|
250
815
|
if [ -n "${EMBEDDING_PORT:-}" ] && [ "$EMBEDDING_PORT" != "$PORT" ]; then
|
|
251
|
-
|
|
252
|
-
'*reflexio.server.llm.embedding_service:app*' \
|
|
253
|
-
'*reflexio*embedding_service*'
|
|
816
|
+
stop_stale_embedding_listener_if_owned || true
|
|
254
817
|
fi
|
|
255
818
|
}
|
|
256
819
|
|
|
257
820
|
case "$CMD" in
|
|
821
|
+
preflight)
|
|
822
|
+
if claude_smart_reflexio_url_is_remote \
|
|
823
|
+
|| [ "${CLAUDE_SMART_BACKEND_AUTOSTART:-1}" = "0" ]; then
|
|
824
|
+
exit 0
|
|
825
|
+
fi
|
|
826
|
+
if ! preflight_bundled_reflexio_runtime; then
|
|
827
|
+
echo "claude-smart backend preflight failed ($VENDOR_PREFLIGHT_FAILURE);" \
|
|
828
|
+
"existing services were not changed" >&2
|
|
829
|
+
echo "Repair the bundled runtime with: npx claude-smart update" >&2
|
|
830
|
+
exit 1
|
|
831
|
+
fi
|
|
832
|
+
exit 0
|
|
833
|
+
;;
|
|
258
834
|
start)
|
|
259
835
|
if claude_smart_is_internal_invocation_env; then
|
|
260
836
|
emit_ok; exit 0
|
|
@@ -268,19 +844,47 @@ case "$CMD" in
|
|
|
268
844
|
if [ "${CLAUDE_SMART_BACKEND_AUTOSTART:-1}" = "0" ]; then
|
|
269
845
|
emit_ok; exit 0
|
|
270
846
|
fi
|
|
271
|
-
if
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
847
|
+
if ! claude_smart_service_lock "backend"; then
|
|
848
|
+
if wait_for_health backend_current_or_compatible 3 1; then
|
|
849
|
+
embedding_current_or_compatible || log_embedding_degraded
|
|
850
|
+
fi
|
|
851
|
+
emit_ok; exit 0
|
|
852
|
+
fi
|
|
853
|
+
trap 'claude_smart_service_unlock "backend"' EXIT
|
|
854
|
+
listener_class="$(classify_backend_listener)"
|
|
855
|
+
case "$listener_class" in
|
|
856
|
+
claude_smart)
|
|
857
|
+
if backend_current_or_compatible; then
|
|
858
|
+
if ! backend_owned_by_current_vendor; then
|
|
859
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
860
|
+
"[claude-smart] backend: accepted compatible claude-smart backend on port $PORT from another plugin root; not restarting"
|
|
861
|
+
fi
|
|
862
|
+
embedding_current_or_compatible || log_embedding_degraded
|
|
863
|
+
emit_ok; exit 0
|
|
864
|
+
fi
|
|
865
|
+
;;
|
|
866
|
+
foreign)
|
|
867
|
+
holder="$(port_holder "$PORT" 2>/dev/null || true)"
|
|
868
|
+
msg="[claude-smart] backend: port $PORT held by another process${holder:+ ($holder)}; skipping start"
|
|
869
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "$msg"
|
|
870
|
+
echo "$msg" >&2
|
|
871
|
+
echo "Free port $PORT (or stop the process above) and run /claude-smart:restart again." >&2
|
|
872
|
+
emit_ok; exit 0
|
|
873
|
+
;;
|
|
874
|
+
esac
|
|
875
|
+
if is_our_backend_running; then
|
|
876
|
+
embedding_current_or_compatible || log_embedding_degraded
|
|
877
|
+
emit_ok; exit 0
|
|
878
|
+
fi
|
|
879
|
+
if recorded_backend_pid_in_startup_grace; then
|
|
880
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
881
|
+
"[claude-smart] backend: recorded backend process is still running but not healthy yet; skipping duplicate start"
|
|
282
882
|
emit_ok; exit 0
|
|
283
883
|
fi
|
|
884
|
+
# Everything above only accepts an existing healthy backend; everything
|
|
885
|
+
# below stops or replaces one. The bundled-runtime checks sit on that
|
|
886
|
+
# boundary: if this plugin cannot run its own backend, report that without
|
|
887
|
+
# tearing down a backend that is currently serving.
|
|
284
888
|
if ! command -v uv >/dev/null 2>&1; then
|
|
285
889
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
286
890
|
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; starting installer in background"
|
|
@@ -293,8 +897,35 @@ case "$CMD" in
|
|
|
293
897
|
emit_ok; exit 0
|
|
294
898
|
fi
|
|
295
899
|
fi
|
|
900
|
+
if ! preflight_bundled_reflexio_runtime; then
|
|
901
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
902
|
+
"[claude-smart] backend: $VENDOR_PREFLIGHT_FAILURE; existing services were not changed"
|
|
903
|
+
emit_start_failure "$VENDOR_PREFLIGHT_FAILURE"
|
|
904
|
+
exit 0
|
|
905
|
+
fi
|
|
906
|
+
if recorded_backend_pid_alive; then
|
|
907
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
908
|
+
"[claude-smart] backend: recorded backend process exceeded startup grace without health; restarting"
|
|
909
|
+
kill_recorded_backend
|
|
910
|
+
sleep 1
|
|
911
|
+
fi
|
|
912
|
+
if stop_stale_backend_listener_if_owned; then
|
|
913
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
|
|
914
|
+
"[claude-smart] backend: existing claude-smart backend is older than plugin $PLUGIN_VERSION; restarting local services"
|
|
915
|
+
fi
|
|
916
|
+
stop_stale_embedding_listener_if_owned || true
|
|
917
|
+
if port_occupied; then
|
|
918
|
+
# is_our_backend_running already ruled out a healthy Reflexio backend.
|
|
919
|
+
# Anything still holding the port would make uvicorn fail to bind, so
|
|
920
|
+
# surface the holder instead of silently starting into a collision.
|
|
921
|
+
holder="$(port_holder "$PORT" 2>/dev/null || true)"
|
|
922
|
+
msg="[claude-smart] backend: port $PORT held by another process${holder:+ ($holder)}; skipping start"
|
|
923
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "$msg"
|
|
924
|
+
echo "$msg" >&2
|
|
925
|
+
echo "Free port $PORT (or stop the process above) and run /claude-smart:restart again." >&2
|
|
926
|
+
emit_ok; exit 0
|
|
927
|
+
fi
|
|
296
928
|
cd "$PLUGIN_ROOT"
|
|
297
|
-
ensure_vendored_reflexio_active
|
|
298
929
|
|
|
299
930
|
# Cap local interaction history to keep the SQLite store small for
|
|
300
931
|
# claude-smart users. Reflexio's library defaults are much higher
|
|
@@ -318,21 +949,6 @@ case "$CMD" in
|
|
|
318
949
|
# without touching the file on disk.
|
|
319
950
|
export REFLEXIO_STORAGE="sqlite"
|
|
320
951
|
|
|
321
|
-
backend_pythonpath="${PYTHONPATH:-}"
|
|
322
|
-
if [ -d "$PLUGIN_ROOT/vendor/reflexio/reflexio" ]; then
|
|
323
|
-
vendor_pythonpath="$PLUGIN_ROOT/vendor/reflexio"
|
|
324
|
-
pythonpath_sep=":"
|
|
325
|
-
if claude_smart_is_windows; then
|
|
326
|
-
# Native Windows Python expects ;-separated Windows-style paths in
|
|
327
|
-
# PYTHONPATH; MSYS does not auto-convert arbitrary env vars.
|
|
328
|
-
pythonpath_sep=";"
|
|
329
|
-
if command -v cygpath >/dev/null 2>&1; then
|
|
330
|
-
vendor_pythonpath="$(cygpath -w "$vendor_pythonpath")"
|
|
331
|
-
fi
|
|
332
|
-
fi
|
|
333
|
-
backend_pythonpath="$vendor_pythonpath${backend_pythonpath:+$pythonpath_sep$backend_pythonpath}"
|
|
334
|
-
fi
|
|
335
|
-
|
|
336
952
|
# (nohup; no process groups). backend-log-runner.sh owns stdout/stderr
|
|
337
953
|
# capture so process output cannot grow backend.log past its cap.
|
|
338
954
|
#
|
|
@@ -344,28 +960,41 @@ case "$CMD" in
|
|
|
344
960
|
# CLAUDE_SMART_BACKEND_WORKERS for power users running concurrent
|
|
345
961
|
# Claude Code sessions or wanting zero-downtime recycling.
|
|
346
962
|
workers="${CLAUDE_SMART_BACKEND_WORKERS:-1}"
|
|
347
|
-
backend_python
|
|
963
|
+
# Sets backend_python / backend_pythonpath for the spawn below. Already run
|
|
964
|
+
# by the preflight above; repeated here so the spawn does not depend on a
|
|
965
|
+
# side effect of an earlier, reorderable step.
|
|
966
|
+
prepare_backend_import_context
|
|
967
|
+
# Record the spawned pid, not a pgid sampled with ps. On POSIX,
|
|
968
|
+
# setsid/python os.setsid make this pid the new process group leader;
|
|
969
|
+
# sampling immediately can race and capture the caller's pgid instead.
|
|
970
|
+
# On Windows, claude_smart_kill_tree translates the MSYS pid to WINPID.
|
|
971
|
+
set -- services start --only backend --no-reload --workers "$workers"
|
|
348
972
|
claude_smart_spawn_detached bash "$HERE/backend-log-runner.sh" \
|
|
349
973
|
"$LOG_FILE" "$LOG_MAX_BYTES" -- \
|
|
350
974
|
env PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}" \
|
|
351
975
|
PYTHONPATH="$backend_pythonpath" \
|
|
352
|
-
|
|
976
|
+
CLAUDE_SMART_BACKEND=1 \
|
|
977
|
+
CLAUDE_SMART_PLUGIN_ROOT="$PLUGIN_ROOT_CANONICAL" \
|
|
978
|
+
CLAUDE_SMART_VERSION="$PLUGIN_VERSION" \
|
|
979
|
+
CLAUDE_SMART_REFLEXIO_VENDOR_ROOT="$VENDORED_REFLEXIO_FOR_PYTHON" \
|
|
980
|
+
"$backend_python" "$HERE/backend-python-runner.py" \
|
|
981
|
+
--claude-smart-backend=1 \
|
|
982
|
+
"--claude-smart-plugin-root=$PLUGIN_ROOT_CANONICAL" \
|
|
983
|
+
"--claude-smart-version=$PLUGIN_VERSION" \
|
|
984
|
+
"--claude-smart-reflexio-vendor-root=$VENDORED_REFLEXIO_FOR_PYTHON" \
|
|
985
|
+
-- "$@"
|
|
353
986
|
svc_pid=$!
|
|
354
|
-
# Record the spawned pid, not a pgid sampled with ps. On POSIX,
|
|
355
|
-
# setsid/python os.setsid make this pid the new process group leader;
|
|
356
|
-
# sampling immediately can race and capture the caller's pgid instead.
|
|
357
|
-
# On Windows, claude_smart_kill_tree translates the MSYS pid to WINPID.
|
|
358
987
|
echo "$svc_pid" > "$PID_FILE"
|
|
359
988
|
|
|
360
|
-
# Give uvicorn
|
|
361
|
-
#
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
989
|
+
# Give uvicorn about 20s to answer /health. The first boot after a fresh
|
|
990
|
+
# checkout may be slower; if it catches up later, the next hook observes it.
|
|
991
|
+
if wait_for_health backend_owned_by_current_vendor 10 1; then
|
|
992
|
+
# The Node installer waits 45s for this script. Each curl probe is also
|
|
993
|
+
# bounded so a wedged listener cannot outlive the caller's timeout budget.
|
|
994
|
+
if ! wait_for_health embedding_owned_by_current_vendor 5 3; then
|
|
995
|
+
log_embedding_degraded
|
|
996
|
+
fi
|
|
997
|
+
else
|
|
369
998
|
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
370
999
|
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
|
|
371
1000
|
reason=$(tail -n 120 "$LOG_FILE" 2>/dev/null | grep -E "No LLM provider available|No generation-capable LLM provider available|CLI not found|skipping provider registration|Application startup failed" | tail -n 1 | sed 's/^[[:space:]]*//')
|
|
@@ -391,8 +1020,22 @@ case "$CMD" in
|
|
|
391
1020
|
status)
|
|
392
1021
|
if claude_smart_reflexio_url_is_remote; then
|
|
393
1022
|
echo "remote configured at $REFLEXIO_URL"
|
|
394
|
-
elif
|
|
395
|
-
|
|
1023
|
+
elif backend_owned_by_current_vendor; then
|
|
1024
|
+
if embedding_owned_by_current_vendor; then
|
|
1025
|
+
echo "running on http://localhost:$PORT (bundled Reflexio at $VENDORED_REFLEXIO)"
|
|
1026
|
+
else
|
|
1027
|
+
echo "running on http://localhost:$PORT (bundled Reflexio at $VENDORED_REFLEXIO; embedding degraded on http://127.0.0.1:$EMBEDDING_PORT)"
|
|
1028
|
+
fi
|
|
1029
|
+
elif backend_current_or_compatible; then
|
|
1030
|
+
if embedding_current_or_compatible; then
|
|
1031
|
+
echo "running on http://localhost:$PORT (compatible claude-smart backend)"
|
|
1032
|
+
else
|
|
1033
|
+
echo "running on http://localhost:$PORT (compatible claude-smart backend; embedding degraded on http://127.0.0.1:$EMBEDDING_PORT)"
|
|
1034
|
+
fi
|
|
1035
|
+
elif backend_owned_by_stale_claude_smart; then
|
|
1036
|
+
echo "stale claude-smart backend on http://localhost:$PORT (older than plugin $PLUGIN_VERSION)"
|
|
1037
|
+
elif backend_healthy; then
|
|
1038
|
+
echo "foreign or ambiguous backend on http://localhost:$PORT (not managed by claude-smart)"
|
|
396
1039
|
else
|
|
397
1040
|
echo "not running"
|
|
398
1041
|
fi
|