Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3659368927 | |||
| 93811f4657 | |||
| 4d57c35b66 |
+43
-1
@@ -15,6 +15,33 @@ export function resolveSandboxMcpNodeExecPath(overridePath) {
|
|||||||
return process.execPath;
|
return process.execPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOOPBACK_PG_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
||||||
|
|
||||||
|
export function resolveSandboxMcpUserDataPgUrl({
|
||||||
|
portalUrl,
|
||||||
|
mcpUrl,
|
||||||
|
containerized = false,
|
||||||
|
hostGateway = 'host.docker.internal',
|
||||||
|
} = {}) {
|
||||||
|
const explicitMcpUrl = String(mcpUrl ?? '').trim();
|
||||||
|
if (explicitMcpUrl) return explicitMcpUrl;
|
||||||
|
|
||||||
|
const sourceUrl = String(portalUrl ?? '').trim();
|
||||||
|
if (!sourceUrl || !containerized) return sourceUrl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(sourceUrl);
|
||||||
|
if (LOOPBACK_PG_HOSTS.has(parsed.hostname)) {
|
||||||
|
parsed.hostname = String(hostGateway || 'host.docker.internal').trim();
|
||||||
|
}
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
// Preserve the configured value so the MCP reports the real configuration
|
||||||
|
// error instead of silently falling back to its local Unix socket default.
|
||||||
|
return sourceUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveMindSearchMcpServerPath(overridePath) {
|
export function resolveMindSearchMcpServerPath(overridePath) {
|
||||||
const normalized = String(overridePath ?? '').trim();
|
const normalized = String(overridePath ?? '').trim();
|
||||||
if (normalized) return normalized;
|
if (normalized) return normalized;
|
||||||
@@ -311,6 +338,21 @@ function sandboxMcpEnvs(sandboxMcp, mcpTools) {
|
|||||||
if (sandboxMcp.workspaceRoot || localRoot) envs.MINDSPACE_WORKSPACE_ROOT = sandboxMcp.workspaceRoot || localRoot;
|
if (sandboxMcp.workspaceRoot || localRoot) envs.MINDSPACE_WORKSPACE_ROOT = sandboxMcp.workspaceRoot || localRoot;
|
||||||
if (sandboxMcp.workspaceRef) envs.MINDSPACE_WORKSPACE_REF = sandboxMcp.workspaceRef;
|
if (sandboxMcp.workspaceRef) envs.MINDSPACE_WORKSPACE_REF = sandboxMcp.workspaceRef;
|
||||||
if (sandboxMcp.userId) envs.PRIVATE_DATA_USER_ID = sandboxMcp.userId;
|
if (sandboxMcp.userId) envs.PRIVATE_DATA_USER_ID = sandboxMcp.userId;
|
||||||
|
if (mcpTools.includes('private_data_info')) {
|
||||||
|
const userDataPgUrl = resolveSandboxMcpUserDataPgUrl({
|
||||||
|
portalUrl: sandboxMcp.userDataPgUrl ?? process.env.MINDSPACE_USERDATA_PG_URL,
|
||||||
|
mcpUrl: sandboxMcp.userDataMcpPgUrl ?? process.env.MINDSPACE_USERDATA_MCP_PG_URL,
|
||||||
|
containerized: Boolean(sandboxMcp.containerized),
|
||||||
|
hostGateway:
|
||||||
|
sandboxMcp.userDataPgHostGateway ?? process.env.MINDSPACE_USERDATA_MCP_PG_HOST ?? 'host.docker.internal',
|
||||||
|
});
|
||||||
|
envs.MINDSPACE_USERDATA_BACKEND = sandboxMcp.userDataBackend ?? process.env.MINDSPACE_USERDATA_BACKEND ?? 'postgres';
|
||||||
|
if (userDataPgUrl) envs.MINDSPACE_USERDATA_PG_URL = userDataPgUrl;
|
||||||
|
const autoProvision = sandboxMcp.userDataAutoProvision ?? process.env.MINDSPACE_USERDATA_AUTO_PROVISION;
|
||||||
|
if (autoProvision != null && String(autoProvision).trim()) {
|
||||||
|
envs.MINDSPACE_USERDATA_AUTO_PROVISION = String(autoProvision).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const key of [
|
for (const key of [
|
||||||
'DATABASE_URL',
|
'DATABASE_URL',
|
||||||
'MYSQL_HOST',
|
'MYSQL_HOST',
|
||||||
@@ -356,7 +398,7 @@ export function buildAgentExtensionPolicy(
|
|||||||
type: 'stdio',
|
type: 'stdio',
|
||||||
name: 'sandbox-fs',
|
name: 'sandbox-fs',
|
||||||
description:
|
description:
|
||||||
'工作区沙箱文件系统与用户私有数据空间。用户私有数据空间是当前用户唯一的 SQLite 数据库,适合问卷、表单、清单、调研数据和分析中间表;不要用于账号、计费、权限、审计、公开平台数据或跨用户数据。',
|
'工作区沙箱文件系统与用户私有数据空间。用户私有数据空间是当前用户隔离的 PostgreSQL schema,适合问卷、表单、清单、调研数据和分析中间表;不要用于账号、计费、权限、审计、公开平台数据或跨用户数据。',
|
||||||
display_name: 'sandbox-fs',
|
display_name: 'sandbox-fs',
|
||||||
bundled: false,
|
bundled: false,
|
||||||
cmd: resolveSandboxMcpNodeExecPath(sandboxMcp.nodeExecPath),
|
cmd: resolveSandboxMcpNodeExecPath(sandboxMcp.nodeExecPath),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
normalizeCapabilityPatch,
|
normalizeCapabilityPatch,
|
||||||
resolveSandboxMcpNodeExecPath,
|
resolveSandboxMcpNodeExecPath,
|
||||||
resolveSandboxMcpServerPath,
|
resolveSandboxMcpServerPath,
|
||||||
|
resolveSandboxMcpUserDataPgUrl,
|
||||||
sandboxDeveloperTools,
|
sandboxDeveloperTools,
|
||||||
sandboxMcpTools,
|
sandboxMcpTools,
|
||||||
} from './capabilities.mjs';
|
} from './capabilities.mjs';
|
||||||
@@ -28,6 +29,30 @@ test('resolveSandboxMcpServerPath honors container-path override without host fs
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolveSandboxMcpUserDataPgUrl rewrites a portal loopback URL for container MCP access', () => {
|
||||||
|
const resolved = resolveSandboxMcpUserDataPgUrl({
|
||||||
|
portalUrl: 'postgresql://mindspace:secret@127.0.0.1:5433/mindspace_userdata_prod',
|
||||||
|
containerized: true,
|
||||||
|
});
|
||||||
|
const parsed = new URL(resolved);
|
||||||
|
assert.equal(parsed.hostname, 'host.docker.internal');
|
||||||
|
assert.equal(parsed.port, '5433');
|
||||||
|
assert.equal(parsed.pathname, '/mindspace_userdata_prod');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveSandboxMcpUserDataPgUrl preserves native URLs and honors an explicit MCP URL', () => {
|
||||||
|
const portalUrl = 'postgresql://mindspace:secret@127.0.0.1:5433/mindspace_userdata_prod';
|
||||||
|
assert.equal(resolveSandboxMcpUserDataPgUrl({ portalUrl }), portalUrl);
|
||||||
|
assert.equal(
|
||||||
|
resolveSandboxMcpUserDataPgUrl({
|
||||||
|
portalUrl,
|
||||||
|
mcpUrl: 'postgresql://mindspace:secret@pg-proxy.internal:6432/mindspace_userdata_prod',
|
||||||
|
containerized: true,
|
||||||
|
}),
|
||||||
|
'postgresql://mindspace:secret@pg-proxy.internal:6432/mindspace_userdata_prod',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('resolveSandboxMcpNodeExecPath honors container-path override without host fs checks', () => {
|
test('resolveSandboxMcpNodeExecPath honors container-path override without host fs checks', () => {
|
||||||
assert.equal(resolveSandboxMcpNodeExecPath('/usr/local/bin/node'), '/usr/local/bin/node');
|
assert.equal(resolveSandboxMcpNodeExecPath('/usr/local/bin/node'), '/usr/local/bin/node');
|
||||||
assert.equal(resolveSandboxMcpNodeExecPath(''), process.execPath);
|
assert.equal(resolveSandboxMcpNodeExecPath(''), process.execPath);
|
||||||
@@ -274,11 +299,18 @@ test('private_data_space alone exposes private data tools through sandbox MCP',
|
|||||||
serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs',
|
serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs',
|
||||||
sandboxRoot: '/opt/h5/MindSpace/user-1',
|
sandboxRoot: '/opt/h5/MindSpace/user-1',
|
||||||
userId: 'user-1',
|
userId: 'user-1',
|
||||||
|
containerized: true,
|
||||||
|
userDataBackend: 'postgres',
|
||||||
|
userDataPgUrl: 'postgresql://mindspace:secret@127.0.0.1:5433/mindspace_userdata_prod',
|
||||||
|
userDataAutoProvision: '1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
||||||
assert.ok(sandboxExt);
|
assert.ok(sandboxExt);
|
||||||
assert.equal(sandboxExt.envs.PRIVATE_DATA_USER_ID, 'user-1');
|
assert.equal(sandboxExt.envs.PRIVATE_DATA_USER_ID, 'user-1');
|
||||||
|
assert.equal(new URL(sandboxExt.envs.MINDSPACE_USERDATA_PG_URL).hostname, 'host.docker.internal');
|
||||||
|
assert.equal(sandboxExt.envs.MINDSPACE_USERDATA_BACKEND, 'postgres');
|
||||||
|
assert.equal(sandboxExt.envs.MINDSPACE_USERDATA_AUTO_PROVISION, '1');
|
||||||
assert.deepEqual(sandboxExt.available_tools, [
|
assert.deepEqual(sandboxExt.available_tools, [
|
||||||
'private_data_info',
|
'private_data_info',
|
||||||
'private_data_schema',
|
'private_data_schema',
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
const skillText = fs.readFileSync(new URL('./skills/page-data-collect/SKILL.md', import.meta.url), 'utf8');
|
||||||
|
|
||||||
|
test('page-data-collect skill uses PostgreSQL DDL and fails closed on private data errors', () => {
|
||||||
|
assert.match(skillText, /GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY/);
|
||||||
|
assert.match(skillText, /TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP/);
|
||||||
|
assert.doesNotMatch(skillText, /INTEGER PRIMARY KEY AUTOINCREMENT/);
|
||||||
|
assert.match(skillText, /数据层失败必须立即停止/);
|
||||||
|
assert.match(skillText, /禁止继续写 HTML、bind 或发布/);
|
||||||
|
assert.match(skillText, /平台没有延迟补执行队列/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('page-data-collect skill pins deletion to the public client API', () => {
|
||||||
|
assert.match(skillText, /client\.deleteRow\('dataset_name', rowId\)/);
|
||||||
|
assert.match(skillText, /禁止发明 `softDeleteRows`/);
|
||||||
|
assert.match(skillText, /表必须包含 `deleted_at TIMESTAMPTZ`/);
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
const releaseScript = fs.readFileSync(
|
||||||
|
new URL('./scripts/release-portal-runtime-prod.sh', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
|
test('103 release preflight checks host and container access to the user-space PostgreSQL', () => {
|
||||||
|
assert.match(releaseScript, /MINDSPACE_USERDATA_PG_URL/);
|
||||||
|
assert.match(releaseScript, /user-space PostgreSQL URL cannot execute SELECT 1/);
|
||||||
|
assert.match(releaseScript, /host\.docker\.internal/);
|
||||||
|
assert.match(releaseScript, /port: 5433/);
|
||||||
|
assert.match(releaseScript, /cannot reach user-space PostgreSQL/);
|
||||||
|
});
|
||||||
@@ -203,6 +203,7 @@ set -euo pipefail
|
|||||||
missing=0
|
missing=0
|
||||||
|
|
||||||
pg_isready_bin="/opt/homebrew/opt/postgresql@17/bin/pg_isready"
|
pg_isready_bin="/opt/homebrew/opt/postgresql@17/bin/pg_isready"
|
||||||
|
psql_bin="/opt/homebrew/opt/postgresql@17/bin/psql"
|
||||||
if [[ -x "${pg_isready_bin}" ]]; then
|
if [[ -x "${pg_isready_bin}" ]]; then
|
||||||
if ! "${pg_isready_bin}" -h 127.0.0.1 -p 5432 -q 2>/dev/null; then
|
if ! "${pg_isready_bin}" -h 127.0.0.1 -p 5432 -q 2>/dev/null; then
|
||||||
echo "goosed dependency check failed: host PostgreSQL (127.0.0.1:5432) is not accepting connections" >&2
|
echo "goosed dependency check failed: host PostgreSQL (127.0.0.1:5432) is not accepting connections" >&2
|
||||||
@@ -213,6 +214,19 @@ else
|
|||||||
echo "goosed dependency check warning: pg_isready not found; skipping PostgreSQL readiness check" >&2
|
echo "goosed dependency check warning: pg_isready not found; skipping PostgreSQL readiness check" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
portal_env="/Users/john/Project/Memind/.env"
|
||||||
|
userdata_pg_url=""
|
||||||
|
if [[ -f "${portal_env}" ]]; then
|
||||||
|
userdata_pg_url="$(grep -E '^MINDSPACE_USERDATA_PG_URL=' "${portal_env}" | tail -1 | cut -d= -f2- || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "${userdata_pg_url}" ]]; then
|
||||||
|
echo "goosed dependency check failed: Portal .env must set MINDSPACE_USERDATA_PG_URL" >&2
|
||||||
|
missing=1
|
||||||
|
elif [[ -x "${psql_bin}" ]] && ! "${psql_bin}" "${userdata_pg_url}" -Atc 'SELECT 1' >/dev/null 2>&1; then
|
||||||
|
echo "goosed dependency check failed: user-space PostgreSQL URL cannot execute SELECT 1" >&2
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
|
||||||
docker_bin="/opt/homebrew/bin/docker"
|
docker_bin="/opt/homebrew/bin/docker"
|
||||||
goosed_containers=()
|
goosed_containers=()
|
||||||
goosed_indexes=()
|
goosed_indexes=()
|
||||||
@@ -240,6 +254,17 @@ if ((${#goosed_containers[@]} > 0)); then
|
|||||||
echo "goosed dependency check failed: ${container} missing /usr/local/bin/node or /opt/portal/mindspace-sandbox-mcp.mjs" >&2
|
echo "goosed dependency check failed: ${container} missing /usr/local/bin/node or /opt/portal/mindspace-sandbox-mcp.mjs" >&2
|
||||||
missing=1
|
missing=1
|
||||||
fi
|
fi
|
||||||
|
if ! "${docker_bin}" exec "${container}" /usr/local/bin/node -e '
|
||||||
|
const net = require("net");
|
||||||
|
const socket = net.connect({ host: "host.docker.internal", port: 5433 });
|
||||||
|
const fail = () => process.exit(1);
|
||||||
|
socket.setTimeout(3000, fail);
|
||||||
|
socket.once("error", fail);
|
||||||
|
socket.once("connect", () => { socket.end(); process.exit(0); });
|
||||||
|
' >/dev/null 2>&1; then
|
||||||
|
echo "goosed dependency check failed: ${container} cannot reach user-space PostgreSQL at host.docker.internal:5433" >&2
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
if [[ -f /Users/john/Project/Memind/.env ]]; then
|
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_node="$(grep -E '^GOOSED_MCP_NODE_PATH=' /Users/john/Project/Memind/.env | tail -1 | cut -d= -f2- || true)"
|
||||||
|
|||||||
@@ -134,11 +134,11 @@ load_skill → page-data-collect
|
|||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE IF NOT EXISTS survey_responses (
|
CREATE TABLE IF NOT EXISTS survey_responses (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
q1_feature TEXT NOT NULL,
|
q1_feature TEXT NOT NULL,
|
||||||
q2_usage TEXT NOT NULL,
|
q2_usage TEXT NOT NULL,
|
||||||
q3_suggestion TEXT NOT NULL,
|
q3_suggestion TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now', '+8 hours'))
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -157,6 +157,13 @@ CREATE TABLE IF NOT EXISTS survey_responses (
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**数据层失败必须立即停止(fail closed)**:
|
||||||
|
|
||||||
|
- `private_data_execute`、`private_data_register_dataset`、`private_data_schema/query` 任一返回连接错误、权限错误或 `isError: true` 时,禁止继续写 HTML、bind 或发布。
|
||||||
|
- 禁止声称“先准备 HTML,PG 恢复后会自动生效”;平台没有延迟补执行队列。数据库恢复后必须重新执行建表、注册、bind 和交付前自检。
|
||||||
|
- PostgreSQL 连接错误应原样报告,不得把 `/tmp/.s.PGSQL.*`、`ECONNREFUSED` 或 `permission denied` 解释成“稍后会自动恢复”。
|
||||||
|
- dataset 配置了 `soft_delete` 时,表必须包含 `deleted_at TIMESTAMPTZ`;配置了 `own_rows` 时,表必须包含 policy 指定的所有者字段。
|
||||||
|
|
||||||
### 3. 页面层:写 HTML
|
### 3. 页面层:写 HTML
|
||||||
|
|
||||||
- 用 `write_file` / `edit_file` 写入或更新 `public/*.html`
|
- 用 `write_file` / `edit_file` 写入或更新 `public/*.html`
|
||||||
@@ -167,6 +174,14 @@ CREATE TABLE IF NOT EXISTS survey_responses (
|
|||||||
<script src="/assets/page-data-client.js"></script>
|
<script src="/assets/page-data-client.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
客户端只允许调用 `page-data-client.js` 已公开的方法:`listRows`、`getSchema`、`getStats`、`insertRow`、`updateRow`、`deleteRow`、`authenticate`。删除单行使用:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await client.deleteRow('dataset_name', rowId);
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止发明 `softDeleteRows`、`deleteRows` 等不存在的方法;服务端会根据 dataset 的 `soft_delete` 授权把 `deleteRow` 转换为软删除。
|
||||||
|
|
||||||
- **第三方 JS 库**(Chart.js、ECharts 等)禁止写 CDN `https://...`;发布页 CSP 只允许同源脚本。优先用平台预置路径,或下载到 `public/assets/` 后用相对路径引用:
|
- **第三方 JS 库**(Chart.js、ECharts 等)禁止写 CDN `https://...`;发布页 CSP 只允许同源脚本。优先用平台预置路径,或下载到 `public/assets/` 后用相对路径引用:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
@@ -287,6 +302,7 @@ CREATE TABLE IF NOT EXISTS survey_responses (
|
|||||||
3. HTML 含 `page-data-client.js`;已 bind 或发布后平台会注入 pageId
|
3. HTML 含 `page-data-client.js`;已 bind 或发布后平台会注入 pageId
|
||||||
4. HTML **不含** `127.0.0.1:`、`/api/survey/`、`PLACEHOLDER_PAGE_ID`
|
4. HTML **不含** `127.0.0.1:`、`/api/survey/`、`PLACEHOLDER_PAGE_ID`
|
||||||
5. 向用户说明:访客如何提交、管理员如何用口令查看记录
|
5. 向用户说明:访客如何提交、管理员如何用口令查看记录
|
||||||
|
6. HTML 未调用 `softDeleteRows` / `deleteRows` 等客户端不存在的方法;删除使用 `deleteRow(dataset, rowId)`
|
||||||
|
|
||||||
## 回复格式
|
## 回复格式
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -1870,7 +1870,7 @@ export function createUserAuth(pool, options = {}) {
|
|||||||
);
|
);
|
||||||
// Wire up the sandbox MCP for user workspace tools and the private data space.
|
// Wire up the sandbox MCP for user workspace tools and the private data space.
|
||||||
// File operations are OS-bound to the user's workspace; private_data_* tools
|
// File operations are OS-bound to the user's workspace; private_data_* tools
|
||||||
// only touch the user's single SQLite data space inside that workspace.
|
// only touch the user's isolated PostgreSQL schema.
|
||||||
let sandboxMcp = null;
|
let sandboxMcp = null;
|
||||||
if (effectiveCapabilities.static_publish || effectiveCapabilities.private_data_space) {
|
if (effectiveCapabilities.static_publish || effectiveCapabilities.private_data_space) {
|
||||||
try {
|
try {
|
||||||
@@ -1879,6 +1879,13 @@ export function createUserAuth(pool, options = {}) {
|
|||||||
env,
|
env,
|
||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
|
const containerFlag = String(env.GOOSED_MCP_CONTAINERIZED ?? '').trim();
|
||||||
|
const containerized = containerFlag
|
||||||
|
? containerFlag === '1'
|
||||||
|
: Boolean(
|
||||||
|
env.GOOSED_MCP_NODE_PATH &&
|
||||||
|
path.resolve(env.GOOSED_MCP_NODE_PATH) !== path.resolve(process.execPath),
|
||||||
|
);
|
||||||
sandboxMcp = {
|
sandboxMcp = {
|
||||||
// When goosed runs in a container its filesystem is split from the portal's,
|
// When goosed runs in a container its filesystem is split from the portal's,
|
||||||
// so the host paths the portal would otherwise send (node binary, MCP script)
|
// so the host paths the portal would otherwise send (node binary, MCP script)
|
||||||
@@ -1891,6 +1898,12 @@ export function createUserAuth(pool, options = {}) {
|
|||||||
workspaceRef: workspaceCapability.workspaceRef,
|
workspaceRef: workspaceCapability.workspaceRef,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
nodeExecPath: env.GOOSED_MCP_NODE_PATH,
|
nodeExecPath: env.GOOSED_MCP_NODE_PATH,
|
||||||
|
containerized,
|
||||||
|
userDataBackend: env.MINDSPACE_USERDATA_BACKEND,
|
||||||
|
userDataPgUrl: env.MINDSPACE_USERDATA_PG_URL,
|
||||||
|
userDataMcpPgUrl: env.MINDSPACE_USERDATA_MCP_PG_URL,
|
||||||
|
userDataPgHostGateway: env.MINDSPACE_USERDATA_MCP_PG_HOST,
|
||||||
|
userDataAutoProvision: env.MINDSPACE_USERDATA_AUTO_PROVISION,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[getAgentSessionPolicy] sandbox MCP setup failed, falling back:', err?.message);
|
console.warn('[getAgentSessionPolicy] sandbox MCP setup failed, falling back:', err?.message);
|
||||||
|
|||||||
+12
-1
@@ -398,7 +398,15 @@ test('agent session policy preserves sandbox root and exposes workspace ref meta
|
|||||||
const auth = createUserAuth(createAgentPolicyPool(userRow), {
|
const auth = createUserAuth(createAgentPolicyPool(userRow), {
|
||||||
h5Root: root,
|
h5Root: root,
|
||||||
persistSessions: false,
|
persistSessions: false,
|
||||||
env: { GOOSED_SANDBOX_PUBLISH_ROOT: '/srv/goosed-mindspace' },
|
env: {
|
||||||
|
GOOSED_SANDBOX_PUBLISH_ROOT: '/srv/goosed-mindspace',
|
||||||
|
GOOSED_MCP_NODE_PATH: '/usr/local/bin/node',
|
||||||
|
GOOSED_MCP_SERVER_PATH: '/opt/portal/mindspace-sandbox-mcp.mjs',
|
||||||
|
GOOSED_MCP_CONTAINERIZED: '1',
|
||||||
|
MINDSPACE_USERDATA_BACKEND: 'postgres',
|
||||||
|
MINDSPACE_USERDATA_PG_URL: 'postgresql://mindspace:secret@127.0.0.1:5433/mindspace_userdata_prod',
|
||||||
|
MINDSPACE_USERDATA_AUTO_PROVISION: '1',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const policy = await auth.getAgentSessionPolicy(userRow.id);
|
const policy = await auth.getAgentSessionPolicy(userRow.id);
|
||||||
@@ -407,6 +415,9 @@ test('agent session policy preserves sandbox root and exposes workspace ref meta
|
|||||||
assert.equal(sandboxFs.envs.SANDBOX_ROOT, path.resolve('/srv/goosed-mindspace/user-1'));
|
assert.equal(sandboxFs.envs.SANDBOX_ROOT, path.resolve('/srv/goosed-mindspace/user-1'));
|
||||||
assert.equal(sandboxFs.envs.MINDSPACE_WORKSPACE_ROOT, path.resolve(root, 'MindSpace', 'user-1'));
|
assert.equal(sandboxFs.envs.MINDSPACE_WORKSPACE_ROOT, path.resolve(root, 'MindSpace', 'user-1'));
|
||||||
assert.equal(sandboxFs.envs.MINDSPACE_WORKSPACE_REF, 'mindspace://users/user-1/workspace');
|
assert.equal(sandboxFs.envs.MINDSPACE_WORKSPACE_REF, 'mindspace://users/user-1/workspace');
|
||||||
|
assert.equal(new URL(sandboxFs.envs.MINDSPACE_USERDATA_PG_URL).hostname, 'host.docker.internal');
|
||||||
|
assert.equal(sandboxFs.envs.MINDSPACE_USERDATA_BACKEND, 'postgres');
|
||||||
|
assert.equal(sandboxFs.envs.MINDSPACE_USERDATA_AUTO_PROVISION, '1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('admin capabilities include granted platform skills', async () => {
|
test('admin capabilities include granted platform skills', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user