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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Auto-start the claude-smart Next.js dashboard
|
|
3
|
-
#
|
|
2
|
+
# Auto-start the claude-smart Next.js dashboard if it's not already running.
|
|
3
|
+
# Mirrors how claude-mem boots its worker on SessionStart:
|
|
4
4
|
# detached, returns immediately so the hook doesn't block the session.
|
|
5
5
|
#
|
|
6
6
|
# Subcommands:
|
|
7
|
-
# start probe the port; spawn `
|
|
8
|
-
#
|
|
7
|
+
# start probe the port; spawn local `next start` if no claude-smart
|
|
8
|
+
# dashboard is already answering. Never builds in foreground — if
|
|
9
9
|
# .next is missing, logs and bails (Setup is responsible for
|
|
10
10
|
# the build; rerun it or restart Claude Code to retry).
|
|
11
11
|
# stop kill the recorded process group, and (if our dashboard
|
|
@@ -28,7 +28,10 @@ fi
|
|
|
28
28
|
claude_smart_prepend_node_bins
|
|
29
29
|
claude_smart_source_reflexio_env
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
BACKEND_PORT="${BACKEND_PORT:-8071}"
|
|
32
|
+
PORT="${DASHBOARD_PORT:-${PORT:-3001}}"
|
|
33
|
+
export BACKEND_PORT
|
|
34
|
+
claude_smart_derive_reflexio_url_from_backend_port
|
|
32
35
|
|
|
33
36
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
34
37
|
claude_smart_reexec_stable_plugin_root_if_needed "$PLUGIN_ROOT" "dashboard-service.sh" "$@"
|
|
@@ -48,128 +51,98 @@ kill_group() {
|
|
|
48
51
|
claude_smart_kill_tree "$1"
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
dashboard_pid_command_matches() {
|
|
55
|
+
pid="$1"
|
|
56
|
+
cmdline="$(claude_smart_pid_command "$pid" | tr '\n' ' ' || true)"
|
|
57
|
+
[ -n "$cmdline" ] || return 1
|
|
58
|
+
case "$cmdline" in
|
|
59
|
+
*"$DASHBOARD_DIR"*|*next-server*|*next*start*|*node*next*|*claude-smart*dashboard*) return 0 ;;
|
|
60
|
+
*) return 1 ;;
|
|
61
|
+
esac
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
kill_recorded_dashboard() {
|
|
65
|
+
if [ -f "$PID_FILE" ]; then
|
|
66
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
67
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && dashboard_pid_command_matches "$pid"; then
|
|
68
|
+
kill_group "$pid"
|
|
69
|
+
fi
|
|
70
|
+
rm -f "$PID_FILE"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
# True if the marker header served by app/api/health is present on the
|
|
52
75
|
# port. Requires curl — absence is reported as false. This deliberately
|
|
53
76
|
# accepts any claude-smart dashboard, including stale cache versions, so
|
|
54
77
|
# stop/uninstall can safely reap them without touching foreign listeners.
|
|
55
78
|
marker_responds() {
|
|
56
79
|
command -v curl >/dev/null 2>&1 || return 1
|
|
57
|
-
curl -sfI "http://127.0.0.1:$PORT/api/health" 2>/dev/null \
|
|
80
|
+
curl -sfI --connect-timeout 1 --max-time 2 "http://127.0.0.1:$PORT/api/health" 2>/dev/null \
|
|
58
81
|
| grep -qi '^x-claude-smart-dashboard:'
|
|
59
82
|
}
|
|
60
83
|
|
|
61
|
-
dashboard_health_headers() {
|
|
62
|
-
command -v curl >/dev/null 2>&1 || return 1
|
|
63
|
-
curl -sfI --connect-timeout 2 --max-time 5 "http://127.0.0.1:$PORT/api/health" 2>/dev/null
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
header_value_from() {
|
|
67
|
-
header_name="$1"
|
|
68
|
-
awk -v wanted="$header_name" '
|
|
69
|
-
BEGIN { wanted = tolower(wanted) ":" }
|
|
70
|
-
{
|
|
71
|
-
line = $0
|
|
72
|
-
sub(/\r$/, "", line)
|
|
73
|
-
lower = tolower(line)
|
|
74
|
-
if (index(lower, wanted) == 1) {
|
|
75
|
-
sub(/^[^:]*:[[:space:]]*/, "", line)
|
|
76
|
-
print line
|
|
77
|
-
exit
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
canonical_dir() {
|
|
84
|
-
dir="$1"
|
|
85
|
-
(cd "$dir" 2>/dev/null && pwd -P) || printf '%s\n' "$dir"
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
normalize_identity_path() {
|
|
89
|
-
path="$1"
|
|
90
|
-
if claude_smart_is_windows; then
|
|
91
|
-
if command -v cygpath >/dev/null 2>&1; then
|
|
92
|
-
path="$(cygpath -u "$path" 2>/dev/null || printf '%s\n' "$path")"
|
|
93
|
-
else
|
|
94
|
-
path="$(
|
|
95
|
-
printf '%s\n' "$path" | awk '{
|
|
96
|
-
gsub(/\\/, "/")
|
|
97
|
-
if ($0 ~ /^[A-Za-z]:/) {
|
|
98
|
-
drive = tolower(substr($0, 1, 1))
|
|
99
|
-
sub(/^[A-Za-z]:/, "/" drive)
|
|
100
|
-
}
|
|
101
|
-
print
|
|
102
|
-
}'
|
|
103
|
-
)"
|
|
104
|
-
fi
|
|
105
|
-
fi
|
|
106
|
-
while [ "${path%/}" != "$path" ] && [ "$path" != "/" ]; do
|
|
107
|
-
path="${path%/}"
|
|
108
|
-
done
|
|
109
|
-
printf '%s\n' "$path"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# True only if *this plugin root's* dashboard is on the port. The generic
|
|
113
|
-
# x-claude-smart-dashboard marker is intentionally not enough: after an
|
|
114
|
-
# install/update, an older cache can keep serving port 3001 until restarted.
|
|
115
|
-
dashboard_matches_current_root() {
|
|
116
|
-
expected_root="$(normalize_identity_path "$(canonical_dir "$PLUGIN_ROOT")")"
|
|
117
|
-
headers="$(dashboard_health_headers)" || return 1
|
|
118
|
-
printf '%s\n' "$headers" | grep -qi '^x-claude-smart-dashboard:' || return 1
|
|
119
|
-
actual_root="$(printf '%s\n' "$headers" | header_value_from "x-claude-smart-plugin-root")"
|
|
120
|
-
[ -n "$actual_root" ] || return 1
|
|
121
|
-
actual_root="$(normalize_identity_path "$actual_root")"
|
|
122
|
-
[ "$actual_root" = "$expected_root" ]
|
|
123
|
-
}
|
|
124
|
-
|
|
125
84
|
# Kill a claude-smart dashboard listener currently holding the port. Gated by
|
|
126
|
-
# marker_responds so a foreign
|
|
85
|
+
# marker_responds so a foreign listener on the dashboard port is never killed.
|
|
127
86
|
stop_dashboard_listener() {
|
|
128
87
|
marker_responds || return 0
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
sleep 0.2
|
|
136
|
-
done
|
|
137
|
-
kill -KILL "$port_pid" 2>/dev/null || true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
# True only if *our* dashboard is on the port. Uses plugin-root identity so a
|
|
141
|
-
# stale claude-smart listener doesn't cause us to silently skip starting.
|
|
142
|
-
is_our_dashboard_running() {
|
|
143
|
-
if [ -f "$PID_FILE" ]; then
|
|
144
|
-
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
145
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
146
|
-
# PID alive — still verify the port responds with our marker so we
|
|
147
|
-
# don't claim "running" when the server crashed but the group leader
|
|
148
|
-
# lingered.
|
|
149
|
-
if command -v curl >/dev/null 2>&1; then
|
|
150
|
-
dashboard_matches_current_root && return 0
|
|
151
|
-
else
|
|
152
|
-
# No curl — fall back to PID liveness alone.
|
|
153
|
-
return 0
|
|
154
|
-
fi
|
|
155
|
-
fi
|
|
156
|
-
fi
|
|
157
|
-
# No PID or dead PID — probe the port for our marker (recovers after a
|
|
158
|
-
# stale PID file from a crash).
|
|
159
|
-
dashboard_matches_current_root && return 0
|
|
160
|
-
return 1
|
|
88
|
+
kill_recorded_dashboard
|
|
89
|
+
claude_smart_reap_port_listeners_matching "$PORT" \
|
|
90
|
+
'*next-server*' \
|
|
91
|
+
'*next*start*' \
|
|
92
|
+
'*node*next*' \
|
|
93
|
+
'*claude-smart*dashboard*' || true
|
|
161
94
|
}
|
|
162
95
|
|
|
163
96
|
# True if *something* is listening on the port, regardless of marker.
|
|
164
97
|
port_occupied() {
|
|
165
98
|
if command -v curl >/dev/null 2>&1; then
|
|
166
|
-
curl -sf -o /dev/null "http://127.0.0.1:$PORT" 2>/dev/null && return 0
|
|
99
|
+
curl -sf --max-time 2 -o /dev/null "http://127.0.0.1:$PORT" 2>/dev/null && return 0
|
|
167
100
|
# curl with -sfI against a 404/405 still indicates "something answered".
|
|
168
101
|
# Use a connect-only probe as a secondary signal.
|
|
169
102
|
fi
|
|
170
103
|
(echo >"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null
|
|
171
104
|
}
|
|
172
105
|
|
|
106
|
+
resolve_next_bin() {
|
|
107
|
+
if claude_smart_is_windows; then
|
|
108
|
+
for candidate in "$DASHBOARD_DIR/node_modules/.bin/next.cmd" "$DASHBOARD_DIR/node_modules/.bin/next"; do
|
|
109
|
+
[ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
110
|
+
done
|
|
111
|
+
else
|
|
112
|
+
for candidate in "$DASHBOARD_DIR/node_modules/.bin/next" "$DASHBOARD_DIR/node_modules/.bin/next.cmd"; do
|
|
113
|
+
[ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
|
|
114
|
+
done
|
|
115
|
+
fi
|
|
116
|
+
return 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
spawn_dashboard() {
|
|
120
|
+
NEXT_BIN="$(resolve_next_bin || true)"
|
|
121
|
+
if [ -z "$NEXT_BIN" ]; then
|
|
122
|
+
reason="local Next.js binary is missing; run claude-smart install/update to restore dashboard dependencies"
|
|
123
|
+
echo "[claude-smart] dashboard: $reason" >>"$LOG_FILE"
|
|
124
|
+
claude_smart_write_dashboard_unavailable "$reason"
|
|
125
|
+
return 1
|
|
126
|
+
fi
|
|
127
|
+
cd "$DASHBOARD_DIR"
|
|
128
|
+
export CLAUDE_SMART_DASHBOARD_WORKSPACE="$WORKSPACE_CWD"
|
|
129
|
+
# Invoke Next directly so custom DASHBOARD_PORT/PORT values are honored
|
|
130
|
+
# without relying on package.json's fixed start script.
|
|
131
|
+
CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached "$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 >>"$LOG_FILE" 2>&1
|
|
132
|
+
dash_pid=$!
|
|
133
|
+
echo "$dash_pid" > "$PID_FILE"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
wait_for_dashboard_marker() {
|
|
137
|
+
attempts="$1"
|
|
138
|
+
while [ "$attempts" -gt 0 ]; do
|
|
139
|
+
marker_responds && return 0
|
|
140
|
+
attempts=$((attempts - 1))
|
|
141
|
+
sleep 1
|
|
142
|
+
done
|
|
143
|
+
marker_responds
|
|
144
|
+
}
|
|
145
|
+
|
|
173
146
|
case "$CMD" in
|
|
174
147
|
start)
|
|
175
148
|
if claude_smart_is_internal_invocation_env; then
|
|
@@ -181,11 +154,19 @@ case "$CMD" in
|
|
|
181
154
|
emit_ok; exit 0
|
|
182
155
|
fi
|
|
183
156
|
if [ ! -d "$DASHBOARD_DIR" ]; then emit_ok; exit 0; fi
|
|
184
|
-
if
|
|
185
|
-
if
|
|
186
|
-
|
|
187
|
-
|
|
157
|
+
if marker_responds; then claude_smart_clear_dashboard_unavailable; emit_ok; exit 0; fi
|
|
158
|
+
if ! claude_smart_service_lock "dashboard"; then
|
|
159
|
+
for _ in 1 2 3 4 5; do
|
|
160
|
+
if marker_responds; then
|
|
161
|
+
claude_smart_clear_dashboard_unavailable
|
|
162
|
+
emit_ok; exit 0
|
|
163
|
+
fi
|
|
164
|
+
sleep 0.2
|
|
165
|
+
done
|
|
166
|
+
emit_ok; exit 0
|
|
188
167
|
fi
|
|
168
|
+
trap 'claude_smart_service_unlock "dashboard"' EXIT
|
|
169
|
+
if marker_responds; then claude_smart_clear_dashboard_unavailable; emit_ok; exit 0; fi
|
|
189
170
|
if port_occupied; then
|
|
190
171
|
echo "[claude-smart] dashboard: port $PORT held by another process; skipping" >>"$LOG_FILE"
|
|
191
172
|
emit_ok; exit 0
|
|
@@ -221,8 +202,6 @@ case "$CMD" in
|
|
|
221
202
|
emit_ok; exit 0
|
|
222
203
|
fi
|
|
223
204
|
|
|
224
|
-
cd "$DASHBOARD_DIR"
|
|
225
|
-
|
|
226
205
|
# Detach so the hook returns immediately. claude_smart_spawn_detached
|
|
227
206
|
# picks the strongest primitive available:
|
|
228
207
|
# - Linux: setsid (puts child in its own session/group, pid==pgid).
|
|
@@ -230,36 +209,41 @@ case "$CMD" in
|
|
|
230
209
|
# - Windows: nohup alone (no process groups; tree-kill via taskkill).
|
|
231
210
|
# Caller-side `>>file 2>&1` redirection is honoured before the child
|
|
232
211
|
# detaches, so per-OS log paths stay identical.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
212
|
+
if ! spawn_dashboard; then
|
|
213
|
+
emit_ok; exit 0
|
|
214
|
+
fi
|
|
215
|
+
if wait_for_dashboard_marker 5; then
|
|
216
|
+
claude_smart_clear_dashboard_unavailable
|
|
217
|
+
emit_ok; exit 0
|
|
218
|
+
fi
|
|
219
|
+
if ! kill -0 "$dash_pid" 2>/dev/null; then
|
|
220
|
+
sleep 2
|
|
221
|
+
if port_occupied; then
|
|
222
|
+
if marker_responds; then
|
|
223
|
+
claude_smart_clear_dashboard_unavailable
|
|
224
|
+
else
|
|
225
|
+
claude_smart_write_dashboard_unavailable "dashboard process exited and port $PORT is now held by a non-claude-smart listener; see $LOG_FILE"
|
|
226
|
+
fi
|
|
227
|
+
else
|
|
228
|
+
echo "[claude-smart] dashboard: first start exited before readiness; retrying once" >>"$LOG_FILE"
|
|
229
|
+
if spawn_dashboard; then
|
|
230
|
+
if wait_for_dashboard_marker 5; then
|
|
231
|
+
claude_smart_clear_dashboard_unavailable
|
|
232
|
+
else
|
|
233
|
+
claude_smart_write_dashboard_unavailable "dashboard process spawned but did not respond on http://127.0.0.1:$PORT within 5s; see $LOG_FILE"
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
247
236
|
fi
|
|
248
|
-
|
|
249
|
-
done
|
|
250
|
-
if [ "$dashboard_ready" != "1" ]; then
|
|
237
|
+
else
|
|
251
238
|
claude_smart_write_dashboard_unavailable "dashboard process spawned but did not respond on http://127.0.0.1:$PORT within 5s; see $LOG_FILE"
|
|
252
239
|
fi
|
|
253
240
|
emit_ok
|
|
254
241
|
;;
|
|
255
242
|
stop)
|
|
256
|
-
|
|
257
|
-
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
258
|
-
rm -f "$PID_FILE"
|
|
259
|
-
fi
|
|
243
|
+
kill_recorded_dashboard
|
|
260
244
|
# Fallback: if a claude-smart dashboard is still responding on the port (e.g.,
|
|
261
245
|
# was started outside this script, or the PGID kill missed because
|
|
262
|
-
# the process wasn't the group leader)
|
|
246
|
+
# the process wasn't the group leader) reap only dashboard-shaped listeners.
|
|
263
247
|
# Gated on the marker header so we never touch a foreign listener.
|
|
264
248
|
stop_dashboard_listener
|
|
265
249
|
emit_ok
|
|
@@ -269,15 +253,12 @@ case "$CMD" in
|
|
|
269
253
|
# interactions/playbooks between sessions. Opt in to teardown by setting
|
|
270
254
|
# CLAUDE_SMART_DASHBOARD_STOP_ON_END=1 in the environment.
|
|
271
255
|
if [ "${CLAUDE_SMART_DASHBOARD_STOP_ON_END:-0}" = "1" ]; then
|
|
272
|
-
|
|
273
|
-
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
274
|
-
rm -f "$PID_FILE"
|
|
275
|
-
fi
|
|
256
|
+
kill_recorded_dashboard
|
|
276
257
|
fi
|
|
277
258
|
emit_ok
|
|
278
259
|
;;
|
|
279
260
|
status)
|
|
280
|
-
if
|
|
261
|
+
if marker_responds; then echo "running on http://localhost:$PORT"; else echo "not running"; fi
|
|
281
262
|
;;
|
|
282
263
|
*)
|
|
283
264
|
emit_ok
|
|
@@ -8,6 +8,61 @@
|
|
|
8
8
|
# invalid target
|
|
9
9
|
set -eu
|
|
10
10
|
|
|
11
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
if [ -f "$HERE/_lib.sh" ]; then
|
|
13
|
+
# shellcheck source=_lib.sh
|
|
14
|
+
. "$HERE/_lib.sh"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# This script can be invoked without plugin/scripts/_lib.sh; keep these fallbacks
|
|
18
|
+
# in sync with the shared helpers there.
|
|
19
|
+
if ! command -v claude_smart_is_windows >/dev/null 2>&1; then
|
|
20
|
+
claude_smart_is_windows() {
|
|
21
|
+
case "$(uname -s 2>/dev/null)" in
|
|
22
|
+
MINGW*|MSYS*|CYGWIN*) return 0 ;;
|
|
23
|
+
*) return 1 ;;
|
|
24
|
+
esac
|
|
25
|
+
}
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if ! command -v claude_smart_to_windows_path >/dev/null 2>&1; then
|
|
29
|
+
claude_smart_to_windows_path() {
|
|
30
|
+
path="$1"
|
|
31
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
32
|
+
cygpath -w "$path"
|
|
33
|
+
return $?
|
|
34
|
+
fi
|
|
35
|
+
printf '%s\n' "$path" | awk '
|
|
36
|
+
function slash_to_backslash(value) {
|
|
37
|
+
gsub(/\//, "\\", value)
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
function drive_path(drive, rest) {
|
|
41
|
+
drive = toupper(drive)
|
|
42
|
+
sub(/^\//, "", rest)
|
|
43
|
+
if (rest == "") {
|
|
44
|
+
return drive ":\\"
|
|
45
|
+
}
|
|
46
|
+
return drive ":\\" slash_to_backslash(rest)
|
|
47
|
+
}
|
|
48
|
+
{
|
|
49
|
+
normalized = $0
|
|
50
|
+
gsub(/\\/, "/", normalized)
|
|
51
|
+
if (normalized ~ /^[A-Za-z]:($|\/)/) {
|
|
52
|
+
print drive_path(substr(normalized, 1, 1), substr(normalized, 3))
|
|
53
|
+
} else if (normalized ~ /^\/cygdrive\/[A-Za-z]($|\/)/) {
|
|
54
|
+
print drive_path(substr(normalized, 11, 1), substr(normalized, 12))
|
|
55
|
+
} else if (normalized ~ /^\/mnt\/[A-Za-z]($|\/)/) {
|
|
56
|
+
print drive_path(substr(normalized, 6, 1), substr(normalized, 7))
|
|
57
|
+
} else if (normalized ~ /^\/[A-Za-z]($|\/)/) {
|
|
58
|
+
print drive_path(substr(normalized, 2, 1), substr(normalized, 3))
|
|
59
|
+
} else {
|
|
60
|
+
print slash_to_backslash(normalized)
|
|
61
|
+
}
|
|
62
|
+
}'
|
|
63
|
+
}
|
|
64
|
+
fi
|
|
65
|
+
|
|
11
66
|
TARGET="${1:-}"
|
|
12
67
|
FORCE="${2:-}"
|
|
13
68
|
|
|
@@ -25,9 +80,46 @@ TARGET="$(cd "$TARGET" && pwd -P)"
|
|
|
25
80
|
LINK="$HOME/.reflexio/plugin-root"
|
|
26
81
|
mkdir -p "$(dirname "$LINK")"
|
|
27
82
|
|
|
28
|
-
|
|
83
|
+
write_plugin_root_metadata() {
|
|
84
|
+
printf '%s\n' "$TARGET" >"$HOME/.reflexio/plugin-root.txt"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
write_plugin_root_link() {
|
|
88
|
+
reason="$1"
|
|
89
|
+
if claude_smart_is_windows; then
|
|
90
|
+
link_win="$(claude_smart_to_windows_path "$LINK")"
|
|
91
|
+
target_win="$(claude_smart_to_windows_path "$TARGET")"
|
|
92
|
+
if [ -L "$LINK" ] || [ -f "$LINK" ]; then
|
|
93
|
+
rm -f "$LINK"
|
|
94
|
+
elif [ -e "$LINK" ]; then
|
|
95
|
+
# Intentionally avoid rmdir //S //Q here: if this is a real
|
|
96
|
+
# directory rather than a junction, preserve it and use metadata
|
|
97
|
+
# fallback instead of recursively deleting user-owned contents.
|
|
98
|
+
if ! cmd.exe //C rmdir "$link_win" >/dev/null 2>&1; then
|
|
99
|
+
echo "[claude-smart] ensure-plugin-root: could not remove existing Windows junction/path at $LINK" >&2
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
if [ ! -e "$LINK" ] && cmd.exe //C mklink //J "$link_win" "$target_win" >/dev/null 2>&1; then
|
|
103
|
+
write_plugin_root_metadata
|
|
104
|
+
echo "[claude-smart] plugin-root → $TARGET${reason:+ ($reason)}" >&2
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
write_plugin_root_metadata
|
|
108
|
+
if [ -e "$LINK" ]; then
|
|
109
|
+
echo "[claude-smart] ensure-plugin-root: $LINK blocks Windows junction; preserving occupied path and wrote plugin-root.txt fallback" >&2
|
|
110
|
+
else
|
|
111
|
+
echo "[claude-smart] ensure-plugin-root: mklink //J failed; wrote plugin-root.txt fallback" >&2
|
|
112
|
+
fi
|
|
113
|
+
echo "[claude-smart] plugin-root.txt → $TARGET${reason:+ ($reason; junction unavailable)}" >&2
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
29
117
|
ln -sfn "$TARGET" "$LINK"
|
|
30
|
-
echo "[claude-smart] plugin-root → $TARGET (
|
|
118
|
+
echo "[claude-smart] plugin-root → $TARGET${reason:+ ($reason)}" >&2
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if [ "$FORCE" = "--force" ]; then
|
|
122
|
+
write_plugin_root_link "forced"
|
|
31
123
|
exit 0
|
|
32
124
|
fi
|
|
33
125
|
|
|
@@ -43,8 +135,7 @@ if [ -z "$FOLLOW" ] && [ -f "$HOME/.claude-smart/.env" ]; then
|
|
|
43
135
|
FOLLOW="${FOLLOW#\'}"; FOLLOW="${FOLLOW%\'}"
|
|
44
136
|
fi
|
|
45
137
|
if [ "$FOLLOW" = "1" ]; then
|
|
46
|
-
|
|
47
|
-
echo "[claude-smart] plugin-root → $TARGET (follow-session)" >&2
|
|
138
|
+
write_plugin_root_link "follow-session"
|
|
48
139
|
exit 0
|
|
49
140
|
fi
|
|
50
141
|
|
|
@@ -53,16 +144,24 @@ fi
|
|
|
53
144
|
# always retarget it to $TARGET. Plugin updates leave old version
|
|
54
145
|
# directories behind, so a valid pyproject.toml at the stale target is
|
|
55
146
|
# not proof the link is fresh.
|
|
147
|
+
CURRENT=""
|
|
56
148
|
if [ -L "$LINK" ]; then
|
|
57
149
|
# Literal target string, not realpath: we compare against what was written by ln -s.
|
|
58
150
|
CURRENT="$(readlink "$LINK" 2>/dev/null || true)"
|
|
151
|
+
elif claude_smart_is_windows && [ -e "$LINK" ] && [ -f "$HOME/.reflexio/plugin-root.txt" ]; then
|
|
152
|
+
# Windows junctions are not reported as symlinks by Git Bash, so use the
|
|
153
|
+
# metadata written alongside successful junction creation.
|
|
154
|
+
CURRENT="$(cat "$HOME/.reflexio/plugin-root.txt" 2>/dev/null || true)"
|
|
155
|
+
elif claude_smart_is_windows && [ -e "$LINK" ]; then
|
|
156
|
+
echo "[claude-smart] ensure-plugin-root: $LINK is not a symlink and plugin-root.txt is missing; cannot determine whether the Windows plugin-root tracks the active plugin. Run ensure-plugin-root.sh with --force or delete the occupied path to recreate it." >&2
|
|
157
|
+
fi
|
|
158
|
+
if [ -n "$CURRENT" ]; then
|
|
59
159
|
case "$CURRENT" in
|
|
60
160
|
"$HOME/.claude/plugins/cache/"*|"$HOME/.codex/plugins/cache/"*)
|
|
61
161
|
CURRENT_NORM="${CURRENT%/}"
|
|
62
162
|
TARGET_NORM="${TARGET%/}"
|
|
63
163
|
if [ "$CURRENT_NORM" != "$TARGET_NORM" ]; then
|
|
64
|
-
|
|
65
|
-
echo "[claude-smart] plugin-root → $TARGET (cache-tracking, was $CURRENT)" >&2
|
|
164
|
+
write_plugin_root_link "cache-tracking, was $CURRENT"
|
|
66
165
|
fi
|
|
67
166
|
exit 0
|
|
68
167
|
;;
|
|
@@ -75,5 +174,4 @@ if [ -f "$LINK/pyproject.toml" ]; then
|
|
|
75
174
|
exit 0
|
|
76
175
|
fi
|
|
77
176
|
|
|
78
|
-
|
|
79
|
-
echo "[claude-smart] plugin-root → $TARGET" >&2
|
|
177
|
+
write_plugin_root_link ""
|
|
@@ -44,10 +44,13 @@ claude_smart_source_login_path
|
|
|
44
44
|
# Explicit fallback for the astral.sh installer's default paths, in case
|
|
45
45
|
# the user's login-shell rc hasn't yet been re-sourced to pick them up.
|
|
46
46
|
claude_smart_prepend_astral_bins
|
|
47
|
+
BACKEND_PORT="${BACKEND_PORT:-8071}"
|
|
48
|
+
export BACKEND_PORT
|
|
47
49
|
claude_smart_source_reflexio_env
|
|
48
50
|
|
|
49
51
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
50
52
|
claude_smart_reexec_stable_plugin_root_if_needed "$PLUGIN_ROOT" "hook_entry.sh" "$@"
|
|
53
|
+
claude_smart_derive_reflexio_url_from_backend_port
|
|
51
54
|
|
|
52
55
|
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
|
|
53
56
|
STATE_DIR="$HOME/.claude-smart"
|
|
@@ -193,7 +193,14 @@ function parseOpenCodeJson(stdout) {
|
|
|
193
193
|
chunks.push(event.result);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
return chunks.join("").trim();
|
|
196
|
+
return stripOuterMarkdownFence(chunks.join("").trim());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function stripOuterMarkdownFence(content) {
|
|
200
|
+
// OpenCode can wrap one-shot JSON in a whole-response markdown fence.
|
|
201
|
+
// Strip only that outer fence so streamed and one-shot output normalize alike.
|
|
202
|
+
const match = String(content || "").match(/^```[^\r\n]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/);
|
|
203
|
+
return match ? match[1].trim() : content;
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
function timeoutMs() {
|