Bundle Cursor Chat Bridge into Portal runtime for 103 deploy.
Memind CI / Test, build, and release guards (push) Successful in 6m43s
Memind CI / Test, build, and release guards (push) Successful in 6m43s
Add prod runner and LaunchAgent installer so Tang chatBridge toggle can reach a live OpenAI-compatible bridge after env enablement. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
* Usage:
|
* Usage:
|
||||||
* node scripts/apply-tang-cursor-config-103.mjs
|
* node scripts/apply-tang-cursor-config-103.mjs
|
||||||
* node scripts/apply-tang-cursor-config-103.mjs --apply
|
* node scripts/apply-tang-cursor-config-103.mjs --apply
|
||||||
|
* node scripts/apply-tang-cursor-config-103.mjs --apply --enable-chat-bridge
|
||||||
*/
|
*/
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
@@ -38,6 +39,7 @@ RECOMMENDED.intentAllowlist = intentAllowlistFromFeatures(RECOMMENDED.features);
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const apply = process.argv.includes('--apply');
|
const apply = process.argv.includes('--apply');
|
||||||
|
const enableChatBridge = process.argv.includes('--enable-chat-bridge');
|
||||||
const databaseUrl = execSync(
|
const databaseUrl = execSync(
|
||||||
"ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \"grep '^DATABASE_URL=' /Users/john/Project/Memind/.env | cut -d= -f2-\"",
|
"ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \"grep '^DATABASE_URL=' /Users/john/Project/Memind/.env | cut -d= -f2-\"",
|
||||||
{ encoding: 'utf8' },
|
{ encoding: 'utf8' },
|
||||||
@@ -49,10 +51,24 @@ async function main() {
|
|||||||
'SELECT config_json FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1',
|
'SELECT config_json FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1',
|
||||||
['global'],
|
['global'],
|
||||||
);
|
);
|
||||||
|
const target = {
|
||||||
|
...RECOMMENDED,
|
||||||
|
features: {
|
||||||
|
...RECOMMENDED.features,
|
||||||
|
...(enableChatBridge
|
||||||
|
? { [CURSOR_CHANNEL_FEATURES.CHAT_BRIDGE]: { enabled: true } }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
target.intentAllowlist = intentAllowlistFromFeatures(target.features);
|
||||||
|
if (enableChatBridge) {
|
||||||
|
target.meta = { ...target.meta, notes: '唐用户 Cursor 灰度 — 页面+问卷+定时任务+聊天 Bridge' };
|
||||||
|
}
|
||||||
|
|
||||||
console.log('--- before ---');
|
console.log('--- before ---');
|
||||||
console.log(JSON.stringify(before[0]?.config_json ?? null, null, 2));
|
console.log(JSON.stringify(before[0]?.config_json ?? null, null, 2));
|
||||||
console.log('--- target ---');
|
console.log('--- target ---');
|
||||||
console.log(JSON.stringify(RECOMMENDED, null, 2));
|
console.log(JSON.stringify(target, null, 2));
|
||||||
|
|
||||||
if (!apply) {
|
if (!apply) {
|
||||||
console.log('\n(dry-run) 追加 --apply 写入生产库');
|
console.log('\n(dry-run) 追加 --apply 写入生产库');
|
||||||
@@ -67,7 +83,7 @@ async function main() {
|
|||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
config_json = VALUES(config_json),
|
config_json = VALUES(config_json),
|
||||||
updated_at = VALUES(updated_at)`,
|
updated_at = VALUES(updated_at)`,
|
||||||
[JSON.stringify(RECOMMENDED), now],
|
[JSON.stringify(target), now],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [after] = await pool.query(
|
const [after] = await pool.query(
|
||||||
|
|||||||
@@ -176,6 +176,26 @@ async function bundleDeepseekCompatProxy() {
|
|||||||
await run(esbuildBin, args);
|
await run(esbuildBin, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function bundleCursorChatBridge() {
|
||||||
|
if (!(await exists(esbuildBin))) {
|
||||||
|
throw new Error(`未找到 esbuild: ${esbuildBin}`);
|
||||||
|
}
|
||||||
|
console.log('==> 打包 Cursor Chat Bridge');
|
||||||
|
const args = [
|
||||||
|
'cursor-openai-bridge.mjs',
|
||||||
|
'--bundle',
|
||||||
|
'--platform=node',
|
||||||
|
'--format=esm',
|
||||||
|
`--target=${runtimeNodeTarget}`,
|
||||||
|
'--outfile=.runtime/portal/cursor-openai-bridge.mjs',
|
||||||
|
'--banner:js=import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);',
|
||||||
|
];
|
||||||
|
for (const pkg of externalPackages) {
|
||||||
|
args.push(`--external:${pkg}`);
|
||||||
|
}
|
||||||
|
await run(esbuildBin, args);
|
||||||
|
}
|
||||||
|
|
||||||
async function bundleSandboxMcp() {
|
async function bundleSandboxMcp() {
|
||||||
if (!(await exists(esbuildBin))) {
|
if (!(await exists(esbuildBin))) {
|
||||||
throw new Error(`未找到 esbuild: ${esbuildBin}`);
|
throw new Error(`未找到 esbuild: ${esbuildBin}`);
|
||||||
@@ -430,6 +450,7 @@ async function writeMetadata() {
|
|||||||
'run-memind-portal-candidate.sh',
|
'run-memind-portal-candidate.sh',
|
||||||
'run-memind-canary-proxy-prod.sh',
|
'run-memind-canary-proxy-prod.sh',
|
||||||
'run-deepseek-compat-proxy-candidate.sh',
|
'run-deepseek-compat-proxy-candidate.sh',
|
||||||
|
'run-cursor-chat-bridge-prod.sh',
|
||||||
]) {
|
]) {
|
||||||
const source = path.join(root, 'scripts', scriptName);
|
const source = path.join(root, 'scripts', scriptName);
|
||||||
if (!(await exists(source))) {
|
if (!(await exists(source))) {
|
||||||
@@ -523,6 +544,10 @@ async function writeMetadata() {
|
|||||||
path.join(root, 'scripts', 'install-agent-run-guard-agent.sh'),
|
path.join(root, 'scripts', 'install-agent-run-guard-agent.sh'),
|
||||||
path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'),
|
path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'),
|
||||||
);
|
);
|
||||||
|
await fs.copyFile(
|
||||||
|
path.join(root, 'scripts', 'install-cursor-chat-bridge-agent.sh'),
|
||||||
|
path.join(runtimeRoot, 'scripts', 'install-cursor-chat-bridge-agent.sh'),
|
||||||
|
);
|
||||||
await fs.copyFile(
|
await fs.copyFile(
|
||||||
path.join(root, 'scripts', 'resume-agent-run-prod.sh'),
|
path.join(root, 'scripts', 'resume-agent-run-prod.sh'),
|
||||||
path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'),
|
path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'),
|
||||||
@@ -553,6 +578,7 @@ async function writeMetadata() {
|
|||||||
' wechat-mp.bundle.mjs (esbuild bundle; hot-swappable WeChat MP module)',
|
' wechat-mp.bundle.mjs (esbuild bundle; hot-swappable WeChat MP module)',
|
||||||
' memind-canary-proxy.mjs (immutable-identity stable/candidate router)',
|
' memind-canary-proxy.mjs (immutable-identity stable/candidate router)',
|
||||||
' deepseek-no-think-proxy.mjs (DeepSeek V4 tool-round compatibility proxy)',
|
' deepseek-no-think-proxy.mjs (DeepSeek V4 tool-round compatibility proxy)',
|
||||||
|
' cursor-openai-bridge.mjs (Cursor agent OpenAI-compatible chat bridge)',
|
||||||
'',
|
'',
|
||||||
'Post-deploy validation (scripts/check-mindspace-public-links.mjs):',
|
'Post-deploy validation (scripts/check-mindspace-public-links.mjs):',
|
||||||
' Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.',
|
' Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.',
|
||||||
@@ -625,6 +651,7 @@ async function main() {
|
|||||||
await bundleServer();
|
await bundleServer();
|
||||||
await bundleCanaryProxy();
|
await bundleCanaryProxy();
|
||||||
await bundleDeepseekCompatProxy();
|
await bundleDeepseekCompatProxy();
|
||||||
|
await bundleCursorChatBridge();
|
||||||
await bundleWechatMp();
|
await bundleWechatMp();
|
||||||
await bundleSandboxMcp();
|
await bundleSandboxMcp();
|
||||||
await bundleMindSearchMcp();
|
await bundleMindSearchMcp();
|
||||||
@@ -642,6 +669,7 @@ async function main() {
|
|||||||
path.join(runtimeRoot, 'scripts', 'run-deepseek-compat-proxy-candidate.sh'),
|
path.join(runtimeRoot, 'scripts', 'run-deepseek-compat-proxy-candidate.sh'),
|
||||||
0o755,
|
0o755,
|
||||||
);
|
);
|
||||||
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-cursor-chat-bridge-prod.sh'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'wechat-mp-menu.mjs'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'wechat-mp-menu.mjs'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-stream-runtime.mjs'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-stream-runtime.mjs'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-drain.mjs'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-drain.mjs'), 0o755);
|
||||||
@@ -656,6 +684,7 @@ async function main() {
|
|||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-worker-agent.sh'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-worker-agent.sh'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'agent-run-guard.mjs'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'agent-run-guard.mjs'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'), 0o755);
|
||||||
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-cursor-chat-bridge-agent.sh'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'ensure-goose-session-postgres.sh'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'ensure-goose-session-postgres.sh'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-tool-runtime.mjs'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-tool-runtime.mjs'), 0o755);
|
||||||
|
|||||||
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
|
||||||
|
RUNNER="${ROOT}/scripts/run-cursor-chat-bridge-prod.sh"
|
||||||
|
LABEL="${MEMIND_CURSOR_CHAT_BRIDGE_LABEL:-cn.tkmind.cursor-chat-bridge}"
|
||||||
|
PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist"
|
||||||
|
LOG="$HOME/Library/Logs/memind-cursor-chat-bridge.log"
|
||||||
|
GUI="gui/$(id -u)"
|
||||||
|
PORT="${MEMIND_CURSOR_CHAT_BRIDGE_PORT:-18040}"
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs"
|
||||||
|
|
||||||
|
if [[ ! -x "$NODE_BIN" ]]; then
|
||||||
|
NODE_BIN="$(command -v node)"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$RUNNER" ]]; then
|
||||||
|
echo "cursor chat bridge runner not found: $RUNNER" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
chmod +x "$RUNNER"
|
||||||
|
|
||||||
|
cat > "$PLIST" <<EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>$LABEL</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/bin/bash</string>
|
||||||
|
<string>$RUNNER</string>
|
||||||
|
</array>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>$ROOT</string>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>ThrottleInterval</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>$LOG</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>$LOG</string>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/opt/homebrew/bin:/opt/homebrew/opt/node@24/bin:/usr/local/bin:/usr/bin:/bin</string>
|
||||||
|
<key>MEMIND_CURSOR_CHAT_BRIDGE_ENABLED</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>MEMIND_CURSOR_CHAT_BRIDGE_ENTRYPOINT</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>MEMIND_CURSOR_CHAT_BRIDGE_PORT</key>
|
||||||
|
<string>$PORT</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
plutil -lint "$PLIST"
|
||||||
|
launchctl bootout "$GUI/$LABEL" 2>/dev/null || true
|
||||||
|
launchctl bootstrap "$GUI" "$PLIST"
|
||||||
|
launchctl enable "$GUI/$LABEL"
|
||||||
|
launchctl kickstart -k "$GUI/$LABEL"
|
||||||
|
|
||||||
|
echo "installed $PLIST"
|
||||||
|
echo "runner: $RUNNER"
|
||||||
|
echo "port: $PORT"
|
||||||
|
echo "log: $LOG"
|
||||||
Executable
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
if [[ ! -f "${ROOT}/.env" ]]; then
|
||||||
|
echo "[cursor-chat-bridge] missing .env in ${ROOT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "${ROOT}"
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "${ROOT}/.env"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
export NODE_ENV=production
|
||||||
|
export MEMIND_CURSOR_CHAT_BRIDGE_ENTRYPOINT=1
|
||||||
|
export MEMIND_CURSOR_CHAT_BRIDGE_PORT="${MEMIND_CURSOR_CHAT_BRIDGE_PORT:-18040}"
|
||||||
|
|
||||||
|
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
|
||||||
|
if [[ ! -x "${NODE_BIN}" ]]; then
|
||||||
|
NODE_BIN="$(command -v node)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BRIDGE="${ROOT}/cursor-openai-bridge.mjs"
|
||||||
|
if [[ ! -f "${BRIDGE}" ]]; then
|
||||||
|
echo "[cursor-chat-bridge] bundle not found: ${BRIDGE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "${NODE_BIN}" "${BRIDGE}"
|
||||||
Reference in New Issue
Block a user