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
|
@@ -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).
|
|
@@ -63,6 +65,41 @@ 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
105
|
if [ "${CLAUDE_SMART_HOST:-claude-code}" = "opencode" ]; then
|
|
@@ -71,7 +108,8 @@ if [ -z "${CLAUDE_SMART_CLI_PATH:-}" ]; then
|
|
|
71
108
|
# resolves opencode from CLAUDE_SMART_OPENCODE_PATH or PATH; don't require
|
|
72
109
|
# Git Bash's PATH to match the installer shell's PATH here.
|
|
73
110
|
claude_smart_prepend_node_bins
|
|
74
|
-
|
|
111
|
+
CLAUDE_SMART_CLI_PATH="$(claude_smart_opencode_compat_path "$PLUGIN_ROOT")"
|
|
112
|
+
export CLAUDE_SMART_CLI_PATH
|
|
75
113
|
elif [ "${CLAUDE_SMART_HOST:-claude-code}" = "codex" ]; then
|
|
76
114
|
# Reflexio's provider still calls CLAUDE_SMART_CLI_PATH with Claude CLI
|
|
77
115
|
# flags. Use a small compatibility executable that translates that narrow
|
|
@@ -87,7 +125,12 @@ if [ -z "${CLAUDE_SMART_CLI_PATH:-}" ]; then
|
|
|
87
125
|
fi
|
|
88
126
|
|
|
89
127
|
STATE_DIR="$HOME/.claude-smart"
|
|
90
|
-
|
|
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"
|
|
91
134
|
LOG_FILE="$STATE_DIR/backend.log"
|
|
92
135
|
LOG_MAX_BYTES="$(claude_smart_log_max_bytes)"
|
|
93
136
|
mkdir -p "$STATE_DIR"
|
|
@@ -95,14 +138,19 @@ claude_smart_trim_log_file "$LOG_FILE" "$LOG_MAX_BYTES"
|
|
|
95
138
|
|
|
96
139
|
emit_ok() { claude_smart_emit_continue; }
|
|
97
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
|
+
|
|
98
145
|
emit_start_failure() {
|
|
99
146
|
reason="$1"
|
|
100
147
|
if py=$(claude_smart_resolve_python 2>/dev/null); then
|
|
101
|
-
"$py" - "$reason" <<'PY'
|
|
148
|
+
"$py" - "$reason" "$VENDOR_PREFLIGHT_FAILURE" <<'PY'
|
|
102
149
|
import json
|
|
103
150
|
import sys
|
|
104
151
|
|
|
105
152
|
reason = sys.argv[1].strip()
|
|
153
|
+
vendor_preflight_failure = sys.argv[2]
|
|
106
154
|
message = (
|
|
107
155
|
"> **claude-smart learning backend is not running.** "
|
|
108
156
|
"Interactions are being buffered locally, but learning will not publish "
|
|
@@ -110,11 +158,19 @@ message = (
|
|
|
110
158
|
)
|
|
111
159
|
if reason:
|
|
112
160
|
message += f">\n> Last startup error: `{reason}`\n"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
)
|
|
118
174
|
print(json.dumps({
|
|
119
175
|
"hookSpecificOutput": {
|
|
120
176
|
"hookEventName": "SessionStart",
|
|
@@ -133,29 +189,28 @@ kill_group() {
|
|
|
133
189
|
claude_smart_kill_tree "$1"
|
|
134
190
|
}
|
|
135
191
|
|
|
136
|
-
# True if /health returns 200
|
|
137
|
-
#
|
|
138
|
-
# reflexio on the same port — if you run two reflexio instances on 8071
|
|
139
|
-
# 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.
|
|
140
194
|
backend_healthy() {
|
|
141
195
|
command -v curl >/dev/null 2>&1 || return 1
|
|
142
|
-
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
|
|
143
197
|
}
|
|
144
198
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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"
|
|
159
214
|
}
|
|
160
215
|
|
|
161
216
|
# Best-effort port probe. curl catches responsive HTTP listeners; /dev/tcp
|
|
@@ -168,48 +223,558 @@ port_occupied() {
|
|
|
168
223
|
(echo >"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null
|
|
169
224
|
}
|
|
170
225
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
ours=""
|
|
184
|
-
for pid in $candidates; do
|
|
185
|
-
cmdline=$(ps -p "$pid" -o command= 2>/dev/null || true)
|
|
186
|
-
for pattern in "$@"; do
|
|
187
|
-
if [[ "$cmdline" == $pattern ]]; then
|
|
188
|
-
ours="$ours $pid"
|
|
189
|
-
break
|
|
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
|
|
190
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
|
|
191
278
|
done
|
|
192
279
|
done
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
200
502
|
done
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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")"
|
|
594
|
+
}
|
|
595
|
+
|
|
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.
|
|
607
|
+
is_our_backend_running() {
|
|
608
|
+
if [ -f "$PID_FILE" ]; then
|
|
609
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
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
|
|
616
|
+
fi
|
|
617
|
+
fi
|
|
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
|
|
622
|
+
return 1
|
|
623
|
+
}
|
|
624
|
+
|
|
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"
|
|
636
|
+
}
|
|
637
|
+
|
|
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
|
|
645
|
+
done
|
|
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
|
|
659
|
+
done
|
|
660
|
+
[ -n "$ours" ] || return 0
|
|
661
|
+
stop_backend_pids "$ours"
|
|
204
662
|
}
|
|
205
663
|
|
|
206
664
|
# Describe what (if anything) is currently listening on $1. Returns
|
|
207
|
-
# "<command> (pid <pid>)" or empty if the port is free or
|
|
208
|
-
#
|
|
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.
|
|
209
667
|
port_holder() {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
213
778
|
}
|
|
214
779
|
|
|
215
780
|
ensure_vendored_reflexio_active() {
|
|
@@ -241,27 +806,31 @@ PY
|
|
|
241
806
|
uv pip install --project "$PLUGIN_ROOT" --python "$plugin_python" --quiet --reinstall --no-deps "$vendor" >&2
|
|
242
807
|
}
|
|
243
808
|
|
|
244
|
-
# Full shutdown: kill the recorded process group (if any) then sweep
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
# path so a stale/missing PID file doesn't produce a silent no-op, and
|
|
248
|
-
# so a stale embedding service on EMBEDDING_PORT doesn't block the next
|
|
249
|
-
# fresh boot (e.g. when codex-hook.js falls back to 8072 for the main
|
|
250
|
-
# 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.
|
|
251
812
|
full_stop() {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
rm -f "$PID_FILE"
|
|
255
|
-
fi
|
|
256
|
-
reap_port_listeners "$PORT" '*reflexio*' '*uvicorn*'
|
|
813
|
+
kill_recorded_backend
|
|
814
|
+
stop_backend_listener_if_owned || true
|
|
257
815
|
if [ -n "${EMBEDDING_PORT:-}" ] && [ "$EMBEDDING_PORT" != "$PORT" ]; then
|
|
258
|
-
|
|
259
|
-
'*reflexio.server.llm.embedding_service:app*' \
|
|
260
|
-
'*reflexio*embedding_service*'
|
|
816
|
+
stop_stale_embedding_listener_if_owned || true
|
|
261
817
|
fi
|
|
262
818
|
}
|
|
263
819
|
|
|
264
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
|
+
;;
|
|
265
834
|
start)
|
|
266
835
|
if claude_smart_is_internal_invocation_env; then
|
|
267
836
|
emit_ok; exit 0
|
|
@@ -275,19 +844,47 @@ case "$CMD" in
|
|
|
275
844
|
if [ "${CLAUDE_SMART_BACKEND_AUTOSTART:-1}" = "0" ]; then
|
|
276
845
|
emit_ok; exit 0
|
|
277
846
|
fi
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# user knows which process to quit (common case: editor/dev tool
|
|
283
|
-
# squatting 8071) instead of silently failing.
|
|
284
|
-
holder="$(port_holder "$PORT" 2>/dev/null || true)"
|
|
285
|
-
msg="[claude-smart] backend: port $PORT held by another process${holder:+ ($holder)}; skipping start"
|
|
286
|
-
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "$msg"
|
|
287
|
-
echo "$msg" >&2
|
|
288
|
-
echo "Free port $PORT (or stop the process above) and run /claude-smart:restart again." >&2
|
|
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
|
|
289
851
|
emit_ok; exit 0
|
|
290
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"
|
|
882
|
+
emit_ok; exit 0
|
|
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.
|
|
291
888
|
if ! command -v uv >/dev/null 2>&1; then
|
|
292
889
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
293
890
|
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; starting installer in background"
|
|
@@ -300,8 +897,35 @@ case "$CMD" in
|
|
|
300
897
|
emit_ok; exit 0
|
|
301
898
|
fi
|
|
302
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
|
|
303
928
|
cd "$PLUGIN_ROOT"
|
|
304
|
-
ensure_vendored_reflexio_active
|
|
305
929
|
|
|
306
930
|
# Cap local interaction history to keep the SQLite store small for
|
|
307
931
|
# claude-smart users. Reflexio's library defaults are much higher
|
|
@@ -325,19 +949,6 @@ case "$CMD" in
|
|
|
325
949
|
# without touching the file on disk.
|
|
326
950
|
export REFLEXIO_STORAGE="sqlite"
|
|
327
951
|
|
|
328
|
-
backend_pythonpath="${PYTHONPATH:-}"
|
|
329
|
-
if [ -d "$PLUGIN_ROOT/vendor/reflexio/reflexio" ]; then
|
|
330
|
-
vendor_pythonpath="$PLUGIN_ROOT/vendor/reflexio"
|
|
331
|
-
pythonpath_sep=":"
|
|
332
|
-
if claude_smart_is_windows; then
|
|
333
|
-
# Native Windows Python expects ;-separated Windows-style paths in
|
|
334
|
-
# PYTHONPATH; MSYS does not auto-convert arbitrary env vars.
|
|
335
|
-
pythonpath_sep=";"
|
|
336
|
-
vendor_pythonpath="$(claude_smart_to_windows_path "$vendor_pythonpath")"
|
|
337
|
-
fi
|
|
338
|
-
backend_pythonpath="$vendor_pythonpath${backend_pythonpath:+$pythonpath_sep$backend_pythonpath}"
|
|
339
|
-
fi
|
|
340
|
-
|
|
341
952
|
# (nohup; no process groups). backend-log-runner.sh owns stdout/stderr
|
|
342
953
|
# capture so process output cannot grow backend.log past its cap.
|
|
343
954
|
#
|
|
@@ -349,28 +960,41 @@ case "$CMD" in
|
|
|
349
960
|
# CLAUDE_SMART_BACKEND_WORKERS for power users running concurrent
|
|
350
961
|
# Claude Code sessions or wanting zero-downtime recycling.
|
|
351
962
|
workers="${CLAUDE_SMART_BACKEND_WORKERS:-1}"
|
|
352
|
-
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"
|
|
353
972
|
claude_smart_spawn_detached bash "$HERE/backend-log-runner.sh" \
|
|
354
973
|
"$LOG_FILE" "$LOG_MAX_BYTES" -- \
|
|
355
974
|
env PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}" \
|
|
356
975
|
PYTHONPATH="$backend_pythonpath" \
|
|
357
|
-
|
|
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
|
+
-- "$@"
|
|
358
986
|
svc_pid=$!
|
|
359
|
-
# Record the spawned pid, not a pgid sampled with ps. On POSIX,
|
|
360
|
-
# setsid/python os.setsid make this pid the new process group leader;
|
|
361
|
-
# sampling immediately can race and capture the caller's pgid instead.
|
|
362
|
-
# On Windows, claude_smart_kill_tree translates the MSYS pid to WINPID.
|
|
363
987
|
echo "$svc_pid" > "$PID_FILE"
|
|
364
988
|
|
|
365
|
-
# Give uvicorn
|
|
366
|
-
#
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
|
374
998
|
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
375
999
|
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
|
|
376
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:]]*//')
|
|
@@ -396,8 +1020,22 @@ case "$CMD" in
|
|
|
396
1020
|
status)
|
|
397
1021
|
if claude_smart_reflexio_url_is_remote; then
|
|
398
1022
|
echo "remote configured at $REFLEXIO_URL"
|
|
399
|
-
elif
|
|
400
|
-
|
|
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)"
|
|
401
1039
|
else
|
|
402
1040
|
echo "not running"
|
|
403
1041
|
fi
|