- 数据层:messages 表增加 part_type 字段,新建 file_versions 表支持版本追踪 - 后端:saveWorkspace 版本追踪、saveAgentOutput 源头分离、generateBriefMessage 成员简报 - 后端:applyDocumentEdit 增量编辑、buildWorkflowStep phase-aware 工作流引擎 - API:文件版本查询/回退接口 - 前端:part_type 驱动渲染,产物面板版本历史 - 新增写手团队(主编/搜索员/策划编辑/合规审查员)配置 - store 模块、scheduler 模块、web-search skill Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
318 lines
7.5 KiB
Bash
318 lines
7.5 KiB
Bash
#!/bin/bash
|
|
# Start Web Search Bridge Server
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
PID_FILE="$PROJECT_DIR/.server.pid"
|
|
LOG_FILE="$PROJECT_DIR/.server.log"
|
|
SERVER_ENTRY="dist/server/index.js"
|
|
FORCE_REPAIR="${WEB_SEARCH_FORCE_REPAIR:-0}"
|
|
SERVER_PORT="8923"
|
|
DEFAULT_SERVER_URL="http://127.0.0.1:8923"
|
|
SERVER_URL="${WEB_SEARCH_SERVER:-$DEFAULT_SERVER_URL}"
|
|
HEALTHY_SERVER_URL=""
|
|
|
|
NODE_CMD=""
|
|
NODE_ARGS=()
|
|
NODE_ENV_PREFIX=()
|
|
|
|
resolve_node_runtime() {
|
|
if command -v node > /dev/null 2>&1; then
|
|
NODE_CMD="node"
|
|
NODE_ARGS=()
|
|
NODE_ENV_PREFIX=()
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "${LOBSTERAI_ELECTRON_PATH:-}" ] && [ -x "${LOBSTERAI_ELECTRON_PATH}" ]; then
|
|
NODE_CMD="$LOBSTERAI_ELECTRON_PATH"
|
|
NODE_ARGS=()
|
|
NODE_ENV_PREFIX=("ELECTRON_RUN_AS_NODE=1")
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
http_get() {
|
|
local URL="$1"
|
|
|
|
if command -v curl > /dev/null 2>&1; then
|
|
if curl -s -f "$URL" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if command -v wget > /dev/null 2>&1; then
|
|
if wget -q -O- "$URL" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if ! resolve_node_runtime; then
|
|
return 127
|
|
fi
|
|
|
|
env "${NODE_ENV_PREFIX[@]}" "$NODE_CMD" "${NODE_ARGS[@]}" - "$URL" <<'NODE'
|
|
const [url] = process.argv.slice(2);
|
|
|
|
(async () => {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
process.exit(22);
|
|
}
|
|
process.stdout.write(await response.text());
|
|
} catch {
|
|
process.exit(1);
|
|
}
|
|
})();
|
|
NODE
|
|
}
|
|
|
|
ensure_npm_available() {
|
|
if command -v npm > /dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
echo "✗ npm is unavailable, cannot repair web-search runtime"
|
|
echo " Please reinstall the web-search skill runtime from LobsterAI."
|
|
return 1
|
|
}
|
|
|
|
install_dependencies() {
|
|
echo "Installing dependencies..."
|
|
if ! npm install > /dev/null 2>&1; then
|
|
echo "✗ Failed to install dependencies"
|
|
echo " Check network access and npm logs, then retry."
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
repair_iconv_lite() {
|
|
echo "Repairing incomplete iconv-lite installation..."
|
|
rm -rf "node_modules/iconv-lite"
|
|
if ! npm install --no-save iconv-lite > /dev/null 2>&1; then
|
|
echo "✗ Failed to repair iconv-lite dependency"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
verify_iconv_runtime() {
|
|
env "${NODE_ENV_PREFIX[@]}" "$NODE_CMD" "${NODE_ARGS[@]}" -e "require('./node_modules/iconv-lite/lib/index.js')" > /dev/null 2>&1
|
|
}
|
|
|
|
is_server_build_outdated() {
|
|
if [ ! -f "$SERVER_ENTRY" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$(find server -type f -name '*.ts' -newer "$SERVER_ENTRY" -print -quit 2>/dev/null)" ]; then
|
|
return 0
|
|
fi
|
|
|
|
# Legacy dist builds used a score-based encoding heuristic that can corrupt CJK.
|
|
if grep -q "function scoreDecodedJsonText" "$SERVER_ENTRY" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
kill_listeners_on_server_port() {
|
|
if ! command -v lsof > /dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
local PIDS
|
|
PIDS=$(lsof -ti "tcp:$SERVER_PORT" -sTCP:LISTEN 2>/dev/null | tr '\n' ' ')
|
|
if [ -z "$PIDS" ]; then
|
|
return 0
|
|
fi
|
|
|
|
echo "Force-repair enabled, stopping listeners on port $SERVER_PORT: $PIDS"
|
|
for PID in $PIDS; do
|
|
kill "$PID" > /dev/null 2>&1 || true
|
|
done
|
|
|
|
sleep 1
|
|
|
|
for PID in $PIDS; do
|
|
if ps -p "$PID" > /dev/null 2>&1; then
|
|
kill -9 "$PID" > /dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
is_bridge_server_healthy_at() {
|
|
local BASE_URL="${1%/}"
|
|
local HEALTH_URL="$BASE_URL/api/health"
|
|
local HEALTH_RESPONSE
|
|
HEALTH_RESPONSE=$(http_get "$HEALTH_URL" || true)
|
|
|
|
if echo "$HEALTH_RESPONSE" | grep -q '"success":true'; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
detect_healthy_bridge_server() {
|
|
local CANDIDATES=("$SERVER_URL")
|
|
|
|
if [ "$SERVER_URL" != "$DEFAULT_SERVER_URL" ]; then
|
|
CANDIDATES+=("$DEFAULT_SERVER_URL")
|
|
fi
|
|
|
|
for CANDIDATE in "${CANDIDATES[@]}"; do
|
|
if is_bridge_server_healthy_at "$CANDIDATE"; then
|
|
HEALTHY_SERVER_URL="$CANDIDATE"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# If no force repair is requested and another healthy bridge is already running
|
|
# on the target port, treat it as running even when PID file is missing.
|
|
if [ "$FORCE_REPAIR" != "1" ] && detect_healthy_bridge_server; then
|
|
echo "✓ Bridge Server is already running (detected via health endpoint: ${HEALTHY_SERVER_URL%/}/api/health)"
|
|
exit 0
|
|
fi
|
|
|
|
# Check if server is already running
|
|
if [ -f "$PID_FILE" ]; then
|
|
PID=$(cat "$PID_FILE")
|
|
if ps -p "$PID" > /dev/null 2>&1; then
|
|
if [ "$FORCE_REPAIR" = "1" ]; then
|
|
echo "Force-repair enabled, stopping existing Bridge Server (PID: $PID)..."
|
|
kill "$PID" > /dev/null 2>&1 || true
|
|
sleep 1
|
|
if ps -p "$PID" > /dev/null 2>&1; then
|
|
kill -9 "$PID" > /dev/null 2>&1 || true
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
else
|
|
echo "✓ Bridge Server is already running (PID: $PID)"
|
|
exit 0
|
|
fi
|
|
else
|
|
# Stale PID file, remove it
|
|
rm "$PID_FILE"
|
|
fi
|
|
fi
|
|
|
|
if [ "$FORCE_REPAIR" = "1" ]; then
|
|
kill_listeners_on_server_port
|
|
fi
|
|
|
|
# Start the server in background
|
|
echo "Starting Bridge Server..."
|
|
cd "$PROJECT_DIR"
|
|
|
|
if ! resolve_node_runtime; then
|
|
echo "✗ Failed to start Bridge Server"
|
|
echo " Node.js runtime not found."
|
|
echo " Please install Node.js, or run from LobsterAI so scripts can use Electron runtime."
|
|
exit 1
|
|
fi
|
|
|
|
# Verify a critical transitive dependency before deciding whether to reinstall.
|
|
# Some historical installs had partial node_modules trees (missing iconv-lite encodings).
|
|
ICONV_SENTINEL="node_modules/iconv-lite/encodings/index.js"
|
|
|
|
if [ "$FORCE_REPAIR" = "1" ]; then
|
|
if ! ensure_npm_available; then
|
|
exit 1
|
|
fi
|
|
if ! repair_iconv_lite; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Ensure dependencies are installed
|
|
if [ ! -d "node_modules" ] || [ ! -f "$ICONV_SENTINEL" ]; then
|
|
if ! ensure_npm_available; then
|
|
exit 1
|
|
fi
|
|
if ! install_dependencies; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# npm install may succeed while keeping a corrupted cached package.
|
|
if [ ! -f "$ICONV_SENTINEL" ]; then
|
|
if ! ensure_npm_available; then
|
|
exit 1
|
|
fi
|
|
if ! repair_iconv_lite; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ ! -f "$ICONV_SENTINEL" ]; then
|
|
echo "✗ Dependency check failed: missing $ICONV_SENTINEL"
|
|
echo " Try removing node_modules and reinstalling with network access."
|
|
exit 1
|
|
fi
|
|
|
|
if ! verify_iconv_runtime; then
|
|
if ! ensure_npm_available; then
|
|
exit 1
|
|
fi
|
|
if ! repair_iconv_lite; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! verify_iconv_runtime; then
|
|
echo "✗ iconv-lite runtime verification failed after repair"
|
|
echo " Try removing node_modules and reinstalling with network access."
|
|
exit 1
|
|
fi
|
|
|
|
# Ensure code is compiled and not stale
|
|
if is_server_build_outdated; then
|
|
if ! ensure_npm_available; then
|
|
exit 1
|
|
fi
|
|
echo "Compiling TypeScript (dist missing/outdated)..."
|
|
if ! npm run build > /dev/null 2>&1; then
|
|
echo "✗ Failed to compile TypeScript server"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Start server in background
|
|
nohup env "${NODE_ENV_PREFIX[@]}" "$NODE_CMD" "${NODE_ARGS[@]}" "$SERVER_ENTRY" > "$LOG_FILE" 2>&1 &
|
|
SERVER_PID=$!
|
|
|
|
# Save PID
|
|
echo "$SERVER_PID" > "$PID_FILE"
|
|
|
|
# Wait a moment to check if server started successfully
|
|
sleep 2
|
|
|
|
if ps -p "$SERVER_PID" > /dev/null 2>&1; then
|
|
echo "✓ Bridge Server started successfully (PID: $SERVER_PID)"
|
|
echo " Health check: ${DEFAULT_SERVER_URL}/api/health"
|
|
if [ "$SERVER_URL" != "$DEFAULT_SERVER_URL" ]; then
|
|
echo " Requested endpoint: ${SERVER_URL%/}/api/health"
|
|
fi
|
|
echo " Logs: $LOG_FILE"
|
|
else
|
|
if detect_healthy_bridge_server; then
|
|
echo "✓ Bridge Server is already running (detected via health endpoint: ${HEALTHY_SERVER_URL%/}/api/health)"
|
|
rm -f "$PID_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
echo "✗ Failed to start Bridge Server"
|
|
echo " Check logs: $LOG_FILE"
|
|
rm "$PID_FILE"
|
|
exit 1
|
|
fi
|