fix: align DeepSeek canary tool rounds with gate
Memind CI / Test, build, and release guards (pull_request) Successful in 1m32s

This commit is contained in:
john
2026-07-26 23:49:32 +08:00
parent 046704816f
commit fb6865638a
19 changed files with 519 additions and 20 deletions
+27
View File
@@ -156,6 +156,26 @@ async function bundleCanaryProxy() {
await run(esbuildBin, args);
}
async function bundleDeepseekCompatProxy() {
if (!(await exists(esbuildBin))) {
throw new Error(`未找到 esbuild: ${esbuildBin}`);
}
console.log('==> 打包 DeepSeek 工具轮次兼容代理');
const args = [
'deepseek-no-think-proxy.mjs',
'--bundle',
'--platform=node',
'--format=esm',
`--target=${runtimeNodeTarget}`,
'--outfile=.runtime/portal/deepseek-no-think-proxy.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() {
if (!(await exists(esbuildBin))) {
throw new Error(`未找到 esbuild: ${esbuildBin}`);
@@ -409,6 +429,7 @@ async function writeMetadata() {
for (const scriptName of [
'run-memind-portal-candidate.sh',
'run-memind-canary-proxy-prod.sh',
'run-deepseek-compat-proxy-candidate.sh',
]) {
const source = path.join(root, 'scripts', scriptName);
if (!(await exists(source))) {
@@ -527,6 +548,7 @@ async function writeMetadata() {
' tkmind-excel-mcp.mjs (esbuild bundle; required when Excel Analyst is enabled)',
' wechat-mp.bundle.mjs (esbuild bundle; hot-swappable WeChat MP module)',
' memind-canary-proxy.mjs (immutable-identity stable/candidate router)',
' deepseek-no-think-proxy.mjs (DeepSeek V4 tool-round compatibility proxy)',
'',
'Post-deploy validation (scripts/check-mindspace-public-links.mjs):',
' Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.',
@@ -598,6 +620,7 @@ async function main() {
await copyRuntimeAssets();
await bundleServer();
await bundleCanaryProxy();
await bundleDeepseekCompatProxy();
await bundleWechatMp();
await bundleSandboxMcp();
await bundleMindSearchMcp();
@@ -611,6 +634,10 @@ async function main() {
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-candidate.sh'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-canary-proxy-prod.sh'), 0o755);
await fs.chmod(
path.join(runtimeRoot, 'scripts', 'run-deepseek-compat-proxy-candidate.sh'),
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', 'runtime-worker-drain.mjs'), 0o755);
+65 -2
View File
@@ -13,9 +13,11 @@ RUNTIME_ROOT="${ROOT}/.runtime/portal"
PORTAL_CANDIDATE_LABEL="cn.tkmind.memind-portal-candidate"
CANARY_PROXY_LABEL="cn.tkmind.memind-canary-proxy"
CANARY_TUNNEL_LABEL="cn.tkmind.memind-canary-tunnel"
DEEPSEEK_COMPAT_LABEL="cn.tkmind.memind-deepseek-compat-candidate"
EDGE_HOST="${MEMIND_CANARY_EDGE_HOST:-ssh105-public}"
CANARY_PROXY_PORT=18082
CANARY_TUNNEL_REMOTE_PORT=19082
DEEPSEEK_COMPAT_PORT=18036
EDGE_MOBILE_CONFIG="/etc/nginx/conf.d/m.tkmind.cn.conf"
EDGE_WECHAT_CONFIG="/etc/nginx/conf.d/wechat.m.tkmind.cn.conf"
CANARY_USERNAMES="john"
@@ -46,6 +48,7 @@ Deploys a candidate Portal beside the stable 103 runtime:
canary router 127.0.0.1:18082
candidate Portal 127.0.0.1:18081
candidate goosed 127.0.0.1:18015
DeepSeek compat 0.0.0.0:18036 (host/container only)
The stable runtime is not replaced. A dedicated reverse tunnel exposes the
router only to 105 at 127.0.0.1:19082. The committed release workflow updates
@@ -123,6 +126,7 @@ say "Run release source guards"
required_runtime_paths=(
server.mjs
memind-canary-proxy.mjs
deepseek-no-think-proxy.mjs
wechat-mp.bundle.mjs
mindspace-sandbox-mcp.mjs
tkmind-search-mcp.mjs
@@ -131,6 +135,7 @@ required_runtime_paths=(
package.json
scripts/run-memind-portal-candidate.sh
scripts/run-memind-canary-proxy-prod.sh
scripts/run-deepseek-compat-proxy-candidate.sh
scripts/goosed-canary.compose.yml
scripts/memind-portal-tunnel.sh
)
@@ -155,7 +160,7 @@ printf '%s %s\n' "${bundle_sha}" "$(basename "${BUNDLE_PATH}")" > "${SHA_PATH}"
echo "git_branch=${branch}"
echo "artifact_tree=.runtime/portal"
echo "artifact_bundle_sha256=${bundle_sha}"
echo "routing=stable:8081,proxy:18082,edge-tunnel:19082,candidate:18081,goosed-canary:18015"
echo "routing=stable:8081,proxy:18082,edge-tunnel:19082,candidate:18081,goosed-canary:18015,deepseek-compat:18036"
echo "canary_usernames=${CANARY_USERNAMES}"
echo "canary_wechat_user_ids=${CANARY_WECHAT_USER_IDS}"
echo "manual_environment_changes=none"
@@ -174,6 +179,7 @@ ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
&& test -f '${GOOSED_DIR}/docker-compose.prod.yml' \
&& curl -fsS http://127.0.0.1:8081/api/status >/dev/null \
&& ! lsof -nP -iTCP:${CANARY_PROXY_PORT} -sTCP:LISTEN >/dev/null 2>&1 \
&& ! lsof -nP -iTCP:${DEEPSEEK_COMPAT_PORT} -sTCP:LISTEN >/dev/null 2>&1 \
&& ! lsof -nP -iTCP:18081 -sTCP:LISTEN >/dev/null 2>&1 \
&& ! lsof -nP -iTCP:18015 -sTCP:LISTEN >/dev/null 2>&1 \
&& /opt/homebrew/bin/docker inspect goosed-prod-1 >/dev/null \
@@ -214,9 +220,11 @@ ssh -o BatchMode=yes "${HOST}" \
PORTAL_CANDIDATE_LABEL='${PORTAL_CANDIDATE_LABEL}' \
CANARY_PROXY_LABEL='${CANARY_PROXY_LABEL}' \
CANARY_TUNNEL_LABEL='${CANARY_TUNNEL_LABEL}' \
DEEPSEEK_COMPAT_LABEL='${DEEPSEEK_COMPAT_LABEL}' \
EDGE_HOST='${EDGE_HOST}' \
CANARY_PROXY_PORT='${CANARY_PROXY_PORT}' \
CANARY_TUNNEL_REMOTE_PORT='${CANARY_TUNNEL_REMOTE_PORT}' \
DEEPSEEK_COMPAT_PORT='${DEEPSEEK_COMPAT_PORT}' \
EDGE_MOBILE_CONFIG='${EDGE_MOBILE_CONFIG}' \
EDGE_WECHAT_CONFIG='${EDGE_WECHAT_CONFIG}' \
CANARY_USERNAMES='${CANARY_USERNAMES}' \
@@ -234,6 +242,7 @@ PERSIST_BACKUP_TAR="${BACKUP_DIR}/memind-persisted-${RELEASE_ID}-before-canary.t
CANDIDATE_PLIST="${HOME}/Library/LaunchAgents/${PORTAL_CANDIDATE_LABEL}.plist"
PROXY_PLIST="${HOME}/Library/LaunchAgents/${CANARY_PROXY_LABEL}.plist"
CANARY_TUNNEL_PLIST="${HOME}/Library/LaunchAgents/${CANARY_TUNNEL_LABEL}.plist"
DEEPSEEK_COMPAT_PLIST="${HOME}/Library/LaunchAgents/${DEEPSEEK_COMPAT_LABEL}.plist"
SECRET_FILE="${HOME}/.config/memind/canary-router.secret"
DOCKER_BIN="/opt/homebrew/bin/docker"
GOOSED_COMPOSE="${GOOSED_DIR}/docker-compose.prod.yml"
@@ -260,6 +269,22 @@ candidate_healthy() {
return 1
}
deepseek_compat_healthy() {
curl -fsS --max-time 5 "http://127.0.0.1:${DEEPSEEK_COMPAT_PORT}/health" \
| /opt/homebrew/opt/node@24/bin/node --input-type=module -e '
let input = "";
process.stdin.on("data", (chunk) => { input += chunk; });
process.stdin.on("end", () => {
const status = JSON.parse(input);
if (
status.ok !== true
|| status.contractVersion !== 1
|| status.deepseekThinking !== "disabled"
) process.exit(1);
});
'
}
edge_ssh() {
# This remote release script itself arrives on stdin. Ordinary nested SSH
# commands must not consume the unparsed remainder of that script.
@@ -326,6 +351,7 @@ stop_candidate_services() {
launchctl bootout "${LAUNCHD_GUI}/${CANARY_TUNNEL_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${CANARY_PROXY_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${PORTAL_CANDIDATE_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${DEEPSEEK_COMPAT_LABEL}" >/dev/null 2>&1 || true
"${DOCKER_BIN}" rm -f goosed-prod-canary >/dev/null 2>&1 || true
}
@@ -405,7 +431,8 @@ tar -xzf "${BUNDLE}" -C "${CANDIDATE_DIR}"
cp "${MANIFEST}" "${CANDIDATE_DIR}/.release-manifest.txt"
chmod 755 \
"${CANDIDATE_DIR}/scripts/run-memind-portal-candidate.sh" \
"${CANDIDATE_DIR}/scripts/run-memind-canary-proxy-prod.sh"
"${CANDIDATE_DIR}/scripts/run-memind-canary-proxy-prod.sh" \
"${CANDIDATE_DIR}/scripts/run-deepseek-compat-proxy-candidate.sh"
say "Stop an older canary without touching stable Portal 8081"
restore_edge_to_stable
@@ -434,6 +461,39 @@ done
"${DOCKER_BIN}" exec goosed-prod-canary \
sh -lc 'test -x /usr/local/bin/node && test -f /opt/portal/mindspace-sandbox-mcp.mjs'
say "Start the DeepSeek tool-round compatibility proxy on 18036"
cat > "${DEEPSEEK_COMPAT_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>${DEEPSEEK_COMPAT_LABEL}</string>
<key>ProgramArguments</key>
<array><string>${CANDIDATE_DIR}/scripts/run-deepseek-compat-proxy-candidate.sh</string></array>
<key>EnvironmentVariables</key>
<dict>
<key>MEMIND_CANARY_STABLE_ROOT</key><string>${STABLE_DIR}</string>
<key>MEMIND_CANARY_DEEPSEEK_PROXY_PORT</key><string>${DEEPSEEK_COMPAT_PORT}</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>5</integer>
<key>StandardOutPath</key><string>${HOME}/Library/Logs/memind-deepseek-compat-candidate.log</string>
<key>StandardErrorPath</key><string>${HOME}/Library/Logs/memind-deepseek-compat-candidate.log</string>
</dict>
</plist>
EOF
launchctl bootstrap "${LAUNCHD_GUI}" "${DEEPSEEK_COMPAT_PLIST}" >/dev/null
launchctl kickstart -k "${LAUNCHD_GUI}/${DEEPSEEK_COMPAT_LABEL}" >/dev/null
for _ in $(seq 1 30); do
deepseek_compat_healthy && break
sleep 1
done
deepseek_compat_healthy
"${DOCKER_BIN}" exec goosed-prod-canary \
sh -lc "curl -fsS --max-time 5 http://host.docker.internal:${DEEPSEEK_COMPAT_PORT}/health \
| grep -q '\"deepseekThinking\":\"disabled\"'"
say "Start the passive candidate Portal on 18081"
cat > "${CANDIDATE_PLIST}" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
@@ -449,6 +509,7 @@ cat > "${CANDIDATE_PLIST}" <<EOF
<key>MEMIND_CANARY_RELEASE_ID</key><string>${RELEASE_ID}</string>
<key>MEMIND_CANARY_CANDIDATE_PORT</key><string>18081</string>
<key>MEMIND_CANARY_GOOSED_URL</key><string>https://127.0.0.1:18015</string>
<key>MEMIND_CANARY_DEEPSEEK_PROXY_PORT</key><string>${DEEPSEEK_COMPAT_PORT}</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
@@ -500,6 +561,7 @@ cat > "${PROXY_PLIST}" <<EOF
<key>MEMIND_CANARY_PROXY_PORT</key><string>${CANARY_PROXY_PORT}</string>
<key>MEMIND_RELEASE_CANARY_USERNAMES</key><string>${CANARY_USERNAMES}</string>
<key>MEMIND_RELEASE_CANARY_WECHAT_USER_IDS</key><string>${CANARY_WECHAT_USER_IDS}</string>
<key>MEMIND_CANARY_CANDIDATE_HEALTH_URLS</key><string>http://127.0.0.1:${DEEPSEEK_COMPAT_PORT}/health</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
@@ -675,6 +737,7 @@ printf 'proxy_health=http://127.0.0.1:%s/__memind_canary/health\n' "${CANARY_PRO
printf 'edge_tunnel=http://127.0.0.1:%s/api/status\n' "${CANARY_TUNNEL_REMOTE_PORT}"
printf 'candidate_health=http://127.0.0.1:18081/api/status\n'
printf 'candidate_goosed=https://127.0.0.1:18015/status\n'
printf 'deepseek_compat_health=http://127.0.0.1:%s/health\n' "${DEEPSEEK_COMPAT_PORT}"
REMOTE_SCRIPT
say "103 canary release completed"
+1 -1
View File
@@ -166,7 +166,7 @@ fi
verify_runtime_artifact() {
local missing=0
for required in server.mjs memind-canary-proxy.mjs wechat-mp.bundle.mjs mindspace-sandbox-mcp.mjs tkmind-search-mcp.mjs tkmind-excel-mcp.mjs mindspace-public-links.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/run-memind-portal-candidate.sh scripts/run-memind-canary-proxy-prod.sh scripts/goosed-canary.compose.yml scripts/check-mindspace-public-links.mjs scripts/load-env.mjs scripts/wechat-mp-menu.mjs scripts/memind-portal-tunnel.sh; do
for required in server.mjs memind-canary-proxy.mjs deepseek-no-think-proxy.mjs wechat-mp.bundle.mjs mindspace-sandbox-mcp.mjs tkmind-search-mcp.mjs tkmind-excel-mcp.mjs mindspace-public-links.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/run-memind-portal-candidate.sh scripts/run-memind-canary-proxy-prod.sh scripts/run-deepseek-compat-proxy-candidate.sh scripts/goosed-canary.compose.yml scripts/check-mindspace-public-links.mjs scripts/load-env.mjs scripts/wechat-mp-menu.mjs scripts/memind-portal-tunnel.sh; do
if [[ ! -e "${RUNTIME_ROOT}/${required}" ]]; then
echo "runtime 产物缺失: ${RUNTIME_ROOT}/${required}" >&2
missing=1
+3
View File
@@ -6,6 +6,7 @@ STABLE_DIR="${STUDIO_REMOTE_ROOT:-/Users/john/Project}/Memind"
PORTAL_CANDIDATE_LABEL="cn.tkmind.memind-portal-candidate"
CANARY_PROXY_LABEL="cn.tkmind.memind-canary-proxy"
CANARY_TUNNEL_LABEL="cn.tkmind.memind-canary-tunnel"
DEEPSEEK_COMPAT_LABEL="cn.tkmind.memind-deepseek-compat-candidate"
EDGE_HOST="${MEMIND_CANARY_EDGE_HOST:-ssh105-public}"
CANARY_TUNNEL_REMOTE_PORT=19082
EDGE_MOBILE_CONFIG="/etc/nginx/conf.d/m.tkmind.cn.conf"
@@ -30,6 +31,7 @@ ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
PORTAL_CANDIDATE_LABEL='${PORTAL_CANDIDATE_LABEL}' \
CANARY_PROXY_LABEL='${CANARY_PROXY_LABEL}' \
CANARY_TUNNEL_LABEL='${CANARY_TUNNEL_LABEL}' \
DEEPSEEK_COMPAT_LABEL='${DEEPSEEK_COMPAT_LABEL}' \
EDGE_HOST='${EDGE_HOST}' \
CANARY_TUNNEL_REMOTE_PORT='${CANARY_TUNNEL_REMOTE_PORT}' \
EDGE_MOBILE_CONFIG='${EDGE_MOBILE_CONFIG}' \
@@ -69,6 +71,7 @@ EDGE_ROLLBACK
launchctl bootout "${LAUNCHD_GUI}/${CANARY_TUNNEL_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${CANARY_PROXY_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${PORTAL_CANDIDATE_LABEL}" >/dev/null 2>&1 || true
launchctl bootout "${LAUNCHD_GUI}/${DEEPSEEK_COMPAT_LABEL}" >/dev/null 2>&1 || true
/opt/homebrew/bin/docker rm -f goosed-prod-canary >/dev/null 2>&1 || true
rm -f "${STABLE_DIR}/.release-drain"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
STABLE_ROOT="${MEMIND_CANARY_STABLE_ROOT:-/Users/john/Project/Memind}"
if [[ ! -d "${STABLE_ROOT}" || ! -f "${STABLE_ROOT}/.env" ]]; then
echo "[deepseek-compat] stable runtime environment is unavailable: ${STABLE_ROOT}" >&2
exit 1
fi
cd "${ROOT}"
set -a
# shellcheck disable=SC1091
source "${STABLE_ROOT}/.env"
set +a
export NODE_ENV=production
export MEMIND_DEEPSEEK_PROXY_ENTRYPOINT=1
export MEMIND_DEEPSEEK_NO_THINK_PORT="${MEMIND_CANARY_DEEPSEEK_PROXY_PORT:-18036}"
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
if [[ ! -x "${NODE_BIN}" ]]; then
NODE_BIN="$(command -v node)"
fi
exec "${NODE_BIN}" "${ROOT}/deepseek-no-think-proxy.mjs"
+7
View File
@@ -33,6 +33,13 @@ export TKMIND_API_TARGET="${MEMIND_CANARY_GOOSED_URL:-https://127.0.0.1:18015}"
# mounted at /opt/portal. Never inherit host-only MCP paths from the stable .env.
export GOOSED_MCP_NODE_PATH=/usr/local/bin/node
export GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs
# DeepSeek V4 tool rounds must use the same compatibility contract exercised
# by the release Gate. Stable .env values cannot disable or redirect it.
export MEMIND_DEEPSEEK_DISABLE_THINKING=1
export MEMIND_DEEPSEEK_NO_THINK_PORT="${MEMIND_CANARY_DEEPSEEK_PROXY_PORT:-18036}"
export MEMIND_GOOSED_HOST_GATEWAY=host.docker.internal
unset MEMIND_DEEPSEEK_NO_THINK_BASE_URL
unset MEMIND_DEEPSEEK_NO_THINK_HOST
unset TKMIND_API_TARGET_1
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"