From 1af680c6cb624bc73f0f1413e3fd38a40765342c Mon Sep 17 00:00:00 2001 From: john Date: Sun, 28 Jun 2026 08:20:53 +0800 Subject: [PATCH] chore: align local paths and add colima dev flow --- .env.colima.example | 4 ++ .env.example | 9 ++- README.md | 2 +- capabilities.test.mjs | 17 ++++-- docs/colima-dev-checklist.md | 76 ++++++++++++++++++++++++++ docs/local-dev.md | 4 +- docs/发包必看.md | 27 ++++----- llm-providers.mjs | 17 +++++- scripts/build-portal-runtime.mjs | 5 +- scripts/deploy-to-test-host.sh | 10 ++-- scripts/dev-colima.sh | 15 +++++ scripts/dev-plaza.mjs | 6 +- scripts/dev.mjs | 6 +- scripts/release-portal-runtime-prod.sh | 30 ++++------ scripts/release-prod.sh | 16 +++--- scripts/run-memind-portal-prod.sh | 3 +- scripts/run-plaza-prod.sh | 3 +- scripts/start-plaza-prod.mjs | 6 +- wechat-mp.test.mjs | 4 +- 19 files changed, 190 insertions(+), 70 deletions(-) create mode 100644 .env.colima.example create mode 100644 docs/colima-dev-checklist.md create mode 100755 scripts/dev-colima.sh diff --git a/.env.colima.example b/.env.colima.example new file mode 100644 index 0000000..471bd96 --- /dev/null +++ b/.env.colima.example @@ -0,0 +1,4 @@ +TKMIND_API_TARGET=https://127.0.0.1:18006 +TKMIND_API_TARGETS=https://127.0.0.1:18006 +GOOSED_MCP_NODE_PATH=/usr/local/bin/node +GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs diff --git a/.env.example b/.env.example index 7203e3b..397cf3d 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,11 @@ # 后端代理(server.mjs / vite dev proxy 共用) TKMIND_API_TARGET=https://127.0.0.1:18006 TKMIND_SERVER__SECRET_KEY=local-dev-secret +NODE_BIN=/opt/homebrew/opt/node@24/bin/node +AIDER_BIN=/Users/john/Project/aider/aider.sh +GOOSE_AIDER_BIN=/Users/john/Project/aider/aider.sh +OPENHANDS_BIN=/Users/john/.openhands/bin/studio-openhands +GOOSE_OPENHANDS_BIN=/Users/john/.openhands/bin/studio-openhands H5_PORT=8081 H5_PUBLIC_BASE_URL=http://127.0.0.1:5173 @@ -31,7 +36,7 @@ ADMIN_PUBLIC_HOST=gadm.tkmind.cn # 多用户系统(MySQL 配置后启用;未配置时回退到 H5_ACCESS_PASSWORD) DATABASE_URL=mysql://boot:password@localhost:3306/tkmind # 或分别设置 MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_DATABASE -H5_USERS_ROOT=/root/tkmind_go/users +H5_USERS_ROOT=/Users/john/Project/Memind/users # 新用户赠送余额(人民币分,500 = ¥5.00;勿与美元 cents 混淆) H5_SIGNUP_BALANCE_CENTS=500 H5_ADMIN_USERNAME=admin @@ -122,7 +127,7 @@ H5_ACCESS_PASSWORD=change-me # 前端构建时注入(Vite,需 VITE_ 前缀) # 工作目录:新建会话时使用,必填 -VITE_TKMIND_WORKING_DIR=/Users/john/PycharmProjects/tkmind +VITE_TKMIND_WORKING_DIR=/Users/john/Project/Memind # 可选:覆盖默认 provider / model(未设置则从服务端 config 读取) # VITE_TKMIND_PROVIDER=openai diff --git a/README.md b/README.md index cbc0561..18d1846 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ pnpm open:plaza # 浏览器打开 https://plaza.tkmind.cn/plaza ## 发布规则入口 ```bash -cd /Users/john/PycharmProjects/test/test-memind +cd /Users/john/Project/Memind bash scripts/release-portal-runtime-prod.sh --dry-run bash scripts/release-portal-runtime-prod.sh ``` diff --git a/capabilities.test.mjs b/capabilities.test.mjs index e05fef6..03a7d9a 100644 --- a/capabilities.test.mjs +++ b/capabilities.test.mjs @@ -29,7 +29,10 @@ test('resolveSandboxMcpServerPath honors container-path override without host fs }); test('resolveSandboxMcpNodeExecPath honors container-path override without host fs checks', () => { - assert.equal(resolveSandboxMcpNodeExecPath('/usr/local/bin/node'), '/usr/local/bin/node'); + assert.equal( + resolveSandboxMcpNodeExecPath('/opt/homebrew/opt/node@24/bin/node'), + '/opt/homebrew/opt/node@24/bin/node', + ); assert.equal(resolveSandboxMcpNodeExecPath(''), process.execPath); }); @@ -229,13 +232,17 @@ test('private_data_space alone exposes private data tools through sandbox MCP', test('static_publish with sandboxMcp uses stdio sandbox-fs extension instead of developer', () => { const caps = { ...DEFAULT_USER_CAPABILITIES, static_publish: true }; - const sandboxMcp = { serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs', sandboxRoot: '/opt/h5/MindSpace/abc123' }; + const sandboxMcp = { + serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs', + sandboxRoot: '/opt/h5/MindSpace/abc123', + nodeExecPath: '/opt/homebrew/opt/node@24/bin/node', + }; const policy = buildAgentExtensionPolicy(caps, { sandboxMcp }); const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs'); assert.ok(sandboxExt, 'sandbox-fs extension should be present'); assert.equal(sandboxExt.type, 'stdio'); - assert.equal(sandboxExt.cmd, process.execPath); + assert.equal(sandboxExt.cmd, '/opt/homebrew/opt/node@24/bin/node'); assert.equal(sandboxExt.envs.SANDBOX_ROOT, '/opt/h5/MindSpace/abc123'); assert.equal(sandboxExt.args[1], '/opt/h5/MindSpace/abc123'); // also passed as argv[2] assert.ok(sandboxExt.available_tools.includes('write_file')); @@ -258,10 +265,10 @@ test('sandboxMcp honors container node executable override', () => { sandboxMcp: { serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs', sandboxRoot: '/opt/h5/MindSpace/abc123', - nodeExecPath: '/usr/local/bin/node', + nodeExecPath: '/opt/homebrew/opt/node@24/bin/node', }, }); const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs'); - assert.equal(sandboxExt?.cmd, '/usr/local/bin/node'); + assert.equal(sandboxExt?.cmd, '/opt/homebrew/opt/node@24/bin/node'); }); diff --git a/docs/colima-dev-checklist.md b/docs/colima-dev-checklist.md new file mode 100644 index 0000000..48b3e51 --- /dev/null +++ b/docs/colima-dev-checklist.md @@ -0,0 +1,76 @@ +# Colima Dev Flow + +## 1. 原生开发 + +先在宿主机改代码并确认: + +- `NODE_BIN=/opt/homebrew/opt/node@24/bin/node` +- `AIDER_BIN=/Users/john/Project/aider/aider.sh` +- `OPENHANDS_BIN=/Users/john/.openhands/bin/studio-openhands` + +建议先过: + +```bash +cd /Users/john/Project/Memind +curl -sS http://127.0.0.1:8081/api/status +``` + +## 2. 启动本机 Colima goosed + +```bash +cd /Users/john/Project/tkmind_go +cp .env.colima.example .env.colima +bash scripts/colima-up.sh +curl -sk https://127.0.0.1:18006/status +``` + +必须看到: + +- `ok` + +## 3. 用 Colima goosed 启动本机 H5 + +```bash +cd /Users/john/Project/Memind +cp .env.colima.example .env.colima +bash scripts/dev-colima.sh +``` + +检查: + +```bash +curl -sS http://127.0.0.1:8081/api/status +``` + +## 4. 用 Colima goosed 启动本机管理端 + +```bash +cd /Users/john/Project/memind_adm +cp .env.colima.example .env.colima +bash scripts/local_restart_colima.sh +``` + +检查: + +```bash +curl -sS http://127.0.0.1:8085/health +``` + +## 5. 发布前 smoke + +至少确认: + +1. `https://127.0.0.1:18006/status` 返回 `ok` +2. `http://127.0.0.1:8081/api/status` 返回 `ok` +3. `http://127.0.0.1:8085/health` 返回 `{"ok":true,...}` +4. `GOOSED_MCP_NODE_PATH` 是容器内路径 +5. `GOOSED_MCP_SERVER_PATH` 是容器内路径 +6. `AIDER_BIN`、`GOOSE_AIDER_BIN` 都是 `/Users/john/Project/aider/aider.sh` +7. `OPENHANDS_BIN`、`GOOSE_OPENHANDS_BIN` 都是 `/Users/john/.openhands/bin/studio-openhands` + +## 6. 停止 Colima goosed + +```bash +cd /Users/john/Project/tkmind_go +bash scripts/colima-down.sh +``` diff --git a/docs/local-dev.md b/docs/local-dev.md index 9cfd137..4157415 100644 --- a/docs/local-dev.md +++ b/docs/local-dev.md @@ -2,7 +2,7 @@ > 这是本机本地开发文档,只处理当前工作区里的源码联调,不做任何生产同步。 > `pnpm dev` 不再启动 Plaza;如果需要联动 Plaza,请显式运行 `pnpm dev:all`,或者单独运行 `pnpm dev:plaza` / `pnpm start:plaza`。 -> Plaza 专用脚本的源码默认指向同级仓库 `../test-memindplaza/app/plaza`;如你的目录不同,请用 `PLAZA_APP_DIR` 覆盖。 +> Plaza 专用脚本的源码默认指向 `/Users/john/Project/memind_plaza`;如你的目录不同,请用 `PLAZA_APP_DIR` 覆盖。 > 生产 / 测试 / 预览隔离仍单独看 [生产 / 测试 / 预览隔离规程](./service-isolation-runbook.md)。 `pnpm dev` 启动后,用 **127.0.0.1 + 端口** 访问本仓库自己的服务: @@ -38,7 +38,7 @@ pnpm dev:all | `OPS_PORT` | 3002 | Ops SPA | | `ADMIN_PORT` | 8082 | memind_adm | | `H5_PUBLIC_BASE_URL` | http://127.0.0.1:5173 | 公开链接基址 | -| `PLAZA_APP_DIR` | ../test-memindplaza/app/plaza | Plaza 专用脚本的源码路径(`pnpm dev:plaza` / `pnpm start:plaza` / `pnpm dev:all`) | +| `PLAZA_APP_DIR` | /Users/john/Project/memind_plaza | Plaza 专用脚本的源码路径(`pnpm dev:plaza` / `pnpm start:plaza` / `pnpm dev:all`) | Plaza 本地开发说明见 [plaza-local.md](./plaza-local.md)。生产发布、同步与回滚不要在这里处理,统一看 [生产更新发布指南](./release-deploy.md)。 diff --git a/docs/发包必看.md b/docs/发包必看.md index 19d851a..6bea255 100644 --- a/docs/发包必看.md +++ b/docs/发包必看.md @@ -153,19 +153,25 @@ ssh john@58.38.22.103 'cd /Users/john/Project/Memind && test -d node_modules/htt Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'http-proxy-middleware' ``` -### 4.4 Portal 下发给 goosed 的 MCP 路径必须是容器内路径 +### 4.4 宿主机 Node 与 goosed MCP Node 必须分层配置,且都走显式 env -推荐并默认使用: +103 宿主机脚本不要再依赖 PATH 找 `node`。宿主机统一显式设置: ```bash -GOOSED_MCP_NODE_PATH=/usr/local/bin/node +NODE_BIN=/opt/homebrew/opt/node@24/bin/node +``` + +Portal 下发给 goosed 的 MCP 路径也不要猜,必须在 `.env` 里显式设置容器内路径。当前推荐: + +```bash +GOOSED_MCP_NODE_PATH=/opt/homebrew/opt/node@24/bin/node GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs ``` -不要让新会话继续依赖宿主机路径: +不要让新会话继续依赖宿主机源码路径或 PATH 猜测: ```text -/opt/homebrew/Cellar/node@24/24.16.0/bin/node +/usr/local/bin/node /Users/john/Project/Memind/mindspace-sandbox-mcp.mjs ``` @@ -207,13 +213,10 @@ Provider 'custom_deepseek' is not configured tkmind/goosed:prod-20260627-2125-mcp ``` -这个镜像必须保证以下路径在容器内存在: +这个镜像至少必须保证 `.env` 指向的容器内路径存在。按当前推荐配置,最少要有: ```text -/usr/local/bin/node -/opt/homebrew/bin/node /opt/homebrew/opt/node@24/bin/node -/opt/homebrew/Cellar/node@24/24.16.0/bin/node /opt/portal/mindspace-sandbox-mcp.mjs ``` @@ -231,10 +234,8 @@ ssh john@58.38.22.103 ' for c in goosed-prod-1 goosed-prod-2 goosed-prod-3 goosed-prod-4; do echo "--- $c" /opt/homebrew/bin/docker exec "$c" sh -lc " - ls -l /usr/local/bin/node \ - /opt/homebrew/bin/node \ + ls -l \ /opt/homebrew/opt/node@24/bin/node \ - /opt/homebrew/Cellar/node@24/24.16.0/bin/node \ /opt/portal/mindspace-sandbox-mcp.mjs " done @@ -255,7 +256,7 @@ ssh john@58.38.22.103 ' 3. 103 Portal 进程是否拿到了 `DATABASE_URL`? 4. `~/Library/Logs/memind-portal.log` 是否有 `User auth bootstrap failed`? 5. 18006-18009 是否全 200? -6. goosed 容器内 `/usr/local/bin/node` 和 `/opt/portal/mindspace-sandbox-mcp.mjs` 是否存在? +6. goosed 容器内 `${GOOSED_MCP_NODE_PATH}` 和 `${GOOSED_MCP_SERVER_PATH}` 对应路径是否存在? 7. provider 是否同步到四个 target? 8. 如果是微信服务号或专属 Agent 报错,必须测真实业务路径,不要只看健康接口。 diff --git a/llm-providers.mjs b/llm-providers.mjs index 6461a37..8c4a23f 100644 --- a/llm-providers.mjs +++ b/llm-providers.mjs @@ -436,6 +436,19 @@ function normalizeLaunchMode(mode) { return normalized === 'serve' ? 'serve' : 'headless'; } +function resolveExecutorCommand(executor) { + if (executor === 'openhands') { + const explicit = + String(process.env.GOOSE_OPENHANDS_BIN ?? process.env.OPENHANDS_BIN ?? '').trim(); + if (explicit) return explicit; + } + if (executor === 'aider') { + const explicit = String(process.env.GOOSE_AIDER_BIN ?? process.env.AIDER_BIN ?? '').trim(); + if (explicit) return explicit; + } + return executor; +} + function commandExists(command) { const probe = spawnSync('sh', ['-lc', `command -v ${JSON.stringify(command).slice(1, -1)}`], { stdio: 'ignore', @@ -545,7 +558,7 @@ export function buildExecutorLaunchPlan(runtime, options = {}) { ok: true, executor: 'aider', cwd, - command: 'aider', + command: resolveExecutorCommand('aider'), args: [ '--model', runtime.model, @@ -576,7 +589,7 @@ export function buildExecutorLaunchPlan(runtime, options = {}) { ok: true, executor: 'openhands', cwd, - command: 'openhands', + command: resolveExecutorCommand('openhands'), args, env, notes: [ diff --git a/scripts/build-portal-runtime.mjs b/scripts/build-portal-runtime.mjs index 15ad61d..588d331 100755 --- a/scripts/build-portal-runtime.mjs +++ b/scripts/build-portal-runtime.mjs @@ -203,12 +203,13 @@ async function writeMetadata() { 'export TKMIND_API_TARGETS="${TKMIND_API_TARGETS:-https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009}"', 'export TKMIND_API_TARGET="${TKMIND_API_TARGET:-https://127.0.0.1:18006}"', 'export TKMIND_API_TARGET_1="${TKMIND_API_TARGET_1:-https://127.0.0.1:18007}"', - 'export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-/usr/local/bin/node}"', + 'export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-/opt/homebrew/opt/node@24/bin/node}"', 'export GOOSED_MCP_SERVER_PATH="${GOOSED_MCP_SERVER_PATH:-/opt/portal/mindspace-sandbox-mcp.mjs}"', '', 'NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"', 'if [[ ! -x "${NODE_BIN}" ]]; then', - ' NODE_BIN="$(command -v node)"', + ' echo "NODE_BIN is not executable: ${NODE_BIN}" >&2', + ' exit 1', 'fi', '', 'exec "${NODE_BIN}" "${ROOT}/server.mjs"', diff --git a/scripts/deploy-to-test-host.sh b/scripts/deploy-to-test-host.sh index a86b741..0166423 100755 --- a/scripts/deploy-to-test-host.sh +++ b/scripts/deploy-to-test-host.sh @@ -12,12 +12,12 @@ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" EXCLUDE_FILE="${ROOT_DIR}/scripts/.rsync-exclude-test-host" MEMIND_SRC="${MEMIND_SRC:-${ROOT_DIR}}" -ADM_SRC="${ADM_SRC:-${ROOT_DIR}/../memind_adm}" -PLAZA_SRC="${PLAZA_SRC:-${ROOT_DIR}/../tkmind_go/ui/plaza}" +ADM_SRC="${ADM_SRC:-/Users/john/Project/memind_adm}" +PLAZA_SRC="${PLAZA_SRC:-/Users/john/Project/memind_plaza}" -MEMIND_DST="${MEMIND_DST:-/Users/john/PycharmProjects/test/test-memind}" -ADM_DST="${ADM_DST:-/Users/john/PycharmProjects/test/test-memindadm}" -PLAZA_DST="${PLAZA_DST:-/Users/john/PycharmProjects/test/test-memindplaza}" +MEMIND_DST="${MEMIND_DST:-/Users/john/Project/Memind}" +ADM_DST="${ADM_DST:-/Users/john/Project/memind_adm}" +PLAZA_DST="${PLAZA_DST:-/Users/john/Project/memind_plaza}" RSYNC_OPTS=( -az diff --git a/scripts/dev-colima.sh b/scripts/dev-colima.sh new file mode 100755 index 0000000..0322551 --- /dev/null +++ b/scripts/dev-colima.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +cd "${ROOT}" + +set -a +source "${ROOT}/.env" +if [[ -f "${ROOT}/.env.colima" ]]; then + source "${ROOT}/.env.colima" +fi +set +a + +exec "${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" "${ROOT}/scripts/dev-core.mjs" diff --git a/scripts/dev-plaza.mjs b/scripts/dev-plaza.mjs index 9ded8f2..a84b2b9 100644 --- a/scripts/dev-plaza.mjs +++ b/scripts/dev-plaza.mjs @@ -12,8 +12,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const defaultPlazaDir = fs.existsSync(path.join(root, '../test-memindplaza/app/plaza/package.json')) - ? path.join(root, '../test-memindplaza/app/plaza') +const defaultPlazaDir = fs.existsSync(path.join('/Users/john/Project/memind_plaza', 'package.json')) + ? '/Users/john/Project/memind_plaza' : path.join(root, '../tkmind_go/ui/plaza'); const plazaDir = process.env.PLAZA_APP_DIR ?? defaultPlazaDir; const portalPort = Number(process.env.H5_PORT ?? 8081); @@ -119,7 +119,7 @@ process.on('SIGINT', () => shutdown(0)); process.on('SIGTERM', () => shutdown(0)); if (!fs.existsSync(path.join(plazaDir, 'package.json'))) { - console.error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 ../test-memindplaza/app/plaza`); + console.error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 /Users/john/Project/memind_plaza`); process.exit(1); } diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 0015e74..98aa007 100755 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -7,8 +7,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const defaultPlazaDir = fs.existsSync(path.join(root, '../test-memindplaza/app/plaza/package.json')) - ? path.join(root, '../test-memindplaza/app/plaza') +const defaultPlazaDir = fs.existsSync(path.join('/Users/john/Project/memind_plaza', 'package.json')) + ? '/Users/john/Project/memind_plaza' : path.join(root, '../tkmind_go/ui/plaza'); const plazaDir = process.env.PLAZA_APP_DIR ?? defaultPlazaDir; const opsDir = path.join(root, 'ops'); @@ -125,7 +125,7 @@ const opsEnv = { function ensurePlazaApp() { if (!fs.existsSync(path.join(plazaDir, 'package.json'))) { - throw new Error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 ../test-memindplaza/app/plaza`); + throw new Error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 /Users/john/Project/memind_plaza`); } } diff --git a/scripts/release-portal-runtime-prod.sh b/scripts/release-portal-runtime-prod.sh index 647ee4e..e25af69 100755 --- a/scripts/release-portal-runtime-prod.sh +++ b/scripts/release-portal-runtime-prod.sh @@ -162,6 +162,16 @@ missing=0 docker_bin="/opt/homebrew/bin/docker" if [[ -x "${docker_bin}" ]] && "${docker_bin}" ps --format '{{.Names}}' 2>/dev/null | grep -q '^goosed-prod-1$'; then + portal_mcp_node="" + portal_mcp_server="" + if [[ -f /Users/john/Project/Memind/.env ]]; then + portal_mcp_node="$(grep -E '^GOOSED_MCP_NODE_PATH=' /Users/john/Project/Memind/.env | tail -1 | cut -d= -f2- || true)" + portal_mcp_server="$(grep -E '^GOOSED_MCP_SERVER_PATH=' /Users/john/Project/Memind/.env | tail -1 | cut -d= -f2- || true)" + fi + if [[ -z "${portal_mcp_node}" || -z "${portal_mcp_server}" ]]; then + echo "goosed dependency check failed: Portal .env must set GOOSED_MCP_NODE_PATH and GOOSED_MCP_SERVER_PATH for Docker goosed" >&2 + missing=1 + fi for index in 1 2 3 4; do container="goosed-prod-${index}" host_port=$((18005 + index)) @@ -169,27 +179,11 @@ if [[ -x "${docker_bin}" ]] && "${docker_bin}" ps --format '{{.Names}}' 2>/dev/n echo "goosed dependency check failed: ${container} is not healthy on host port ${host_port}" >&2 missing=1 fi - if ! "${docker_bin}" exec "${container}" sh -lc 'test -x /usr/local/bin/node && test -f /opt/portal/mindspace-sandbox-mcp.mjs' >/dev/null 2>&1; then - echo "goosed dependency check failed: ${container} missing /usr/local/bin/node or /opt/portal/mindspace-sandbox-mcp.mjs" >&2 + if [[ -n "${portal_mcp_node}" && -n "${portal_mcp_server}" ]] && ! "${docker_bin}" exec "${container}" sh -lc "test -x '${portal_mcp_node}' && test -f '${portal_mcp_server}'" >/dev/null 2>&1; then + echo "goosed dependency check failed: ${container} cannot resolve Portal .env MCP paths '${portal_mcp_node}' and '${portal_mcp_server}'" >&2 missing=1 fi done - if [[ -f /Users/john/Project/Memind/.env ]]; then - portal_mcp_node="$(grep -E '^GOOSED_MCP_NODE_PATH=' /Users/john/Project/Memind/.env | tail -1 | cut -d= -f2- || true)" - portal_mcp_server="$(grep -E '^GOOSED_MCP_SERVER_PATH=' /Users/john/Project/Memind/.env | tail -1 | cut -d= -f2- || true)" - if [[ -z "${portal_mcp_node}" || -z "${portal_mcp_server}" ]]; then - echo "goosed dependency check failed: Portal .env must set GOOSED_MCP_NODE_PATH and GOOSED_MCP_SERVER_PATH for Docker goosed" >&2 - missing=1 - else - for index in 1 2 3 4; do - container="goosed-prod-${index}" - if ! "${docker_bin}" exec "${container}" sh -lc "test -x '${portal_mcp_node}' && test -f '${portal_mcp_server}'" >/dev/null 2>&1; then - echo "goosed dependency check failed: ${container} cannot resolve Portal .env MCP paths '${portal_mcp_node}' and '${portal_mcp_server}'" >&2 - missing=1 - fi - done - fi - fi else for port in 18006 18007; do line="$(lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1, $2, $9}')" diff --git a/scripts/release-prod.sh b/scripts/release-prod.sh index 9dff1eb..29d60ac 100755 --- a/scripts/release-prod.sh +++ b/scripts/release-prod.sh @@ -237,13 +237,15 @@ done say "安装依赖" cd "${RELEASE_DIR}" -export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@24/bin:/usr/local/bin:/usr/bin:/bin:${PATH}" -if command -v npm >/dev/null 2>&1; then - NPM_BIN="$(command -v npm)" -elif [[ -x /opt/homebrew/opt/node@24/bin/npm ]]; then - NPM_BIN="/opt/homebrew/opt/node@24/bin/npm" -else - echo "npm not found on remote" >&2 +NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" +if [[ ! -x "${NODE_BIN}" ]]; then + echo "node not found or not executable: ${NODE_BIN}" >&2 + exit 1 +fi +NODE_DIR="$(cd "$(dirname "${NODE_BIN}")" && pwd)" +NPM_BIN="${NPM_BIN:-${NODE_DIR}/npm}" +if [[ ! -x "${NPM_BIN}" ]]; then + echo "npm not found or not executable: ${NPM_BIN}" >&2 exit 1 fi if [[ -f package-lock.json ]]; then diff --git a/scripts/run-memind-portal-prod.sh b/scripts/run-memind-portal-prod.sh index 1f14012..ad163d7 100755 --- a/scripts/run-memind-portal-prod.sh +++ b/scripts/run-memind-portal-prod.sh @@ -21,7 +21,8 @@ export TKMIND_API_TARGET_1="${TKMIND_API_TARGET_1_OVERRIDE:-https://127.0.0.1:18 NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" if [[ ! -x "${NODE_BIN}" ]]; then - NODE_BIN="$(command -v node)" + echo "NODE_BIN is not executable: ${NODE_BIN}" >&2 + exit 1 fi exec "${NODE_BIN}" server.mjs diff --git a/scripts/run-plaza-prod.sh b/scripts/run-plaza-prod.sh index ae934d8..617ef18 100755 --- a/scripts/run-plaza-prod.sh +++ b/scripts/run-plaza-prod.sh @@ -19,7 +19,8 @@ PLAZA_PORT="${PLAZA_PORT:-3001}" NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" if [[ ! -x "${NODE_BIN}" ]]; then - NODE_BIN="$(command -v node)" + echo "NODE_BIN is not executable: ${NODE_BIN}" >&2 + exit 1 fi echo "==> 等待 Portal :${PORTAL_PORT}…" diff --git a/scripts/start-plaza-prod.mjs b/scripts/start-plaza-prod.mjs index 16b805c..27c6989 100644 --- a/scripts/start-plaza-prod.mjs +++ b/scripts/start-plaza-prod.mjs @@ -12,8 +12,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const defaultPlazaDir = fs.existsSync(path.join(root, '../test-memindplaza/app/plaza/package.json')) - ? path.join(root, '../test-memindplaza/app/plaza') +const defaultPlazaDir = fs.existsSync(path.join('/Users/john/Project/memind_plaza', 'package.json')) + ? '/Users/john/Project/memind_plaza' : path.join(root, '../tkmind_go/ui/plaza'); const plazaDir = process.env.PLAZA_APP_DIR ?? defaultPlazaDir; const portalPort = Number(process.env.H5_PORT ?? 8081); @@ -102,7 +102,7 @@ process.on('SIGINT', () => shutdown(0)); process.on('SIGTERM', () => shutdown(0)); if (!fs.existsSync(path.join(plazaDir, 'package.json'))) { - console.error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 ../test-memindplaza/app/plaza`); + console.error(`未找到 Plaza Next.js:${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 /Users/john/Project/memind_plaza`); process.exit(1); } diff --git a/wechat-mp.test.mjs b/wechat-mp.test.mjs index 318deee..cc0c407 100644 --- a/wechat-mp.test.mjs +++ b/wechat-mp.test.mjs @@ -990,7 +990,7 @@ test('wechat mp service reconciles existing dedicated session before reply', asy const extensionConfig = { type: 'stdio', name: 'sandbox-fs', - cmd: '/usr/local/bin/node', + cmd: '/opt/homebrew/opt/node@24/bin/node', args: ['/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs', workingDir], envs: { SANDBOX_ROOT: workingDir }, available_tools: ['list_dir'], @@ -1852,7 +1852,7 @@ test('wechat mp service persists image and routes image url into agent prompt', await result.task; } finally { crypto.randomUUID = originalRandomUuid; - fs.rmSync(`/Users/john/PycharmProjects/test/test-memind/MindSpace/${testUserId}`, { + fs.rmSync(`/Users/john/Project/Memind/MindSpace/${testUserId}`, { recursive: true, force: true, });