Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import path from 'node:path';
|
||||
import { developerToolsFromPolicy } from './capabilities.mjs';
|
||||
import { buildSandboxSessionConstraints } from './user-publish.mjs';
|
||||
|
||||
function extensionName(config) {
|
||||
return config?.name ?? null;
|
||||
}
|
||||
|
||||
export function allowedExtensionNames(extensionOverrides) {
|
||||
if (!extensionOverrides) return null;
|
||||
return new Set(extensionOverrides.map((item) => item.name).filter(Boolean));
|
||||
}
|
||||
|
||||
export function extensionToolsKey(config) {
|
||||
const tools = config?.available_tools ?? config?.availableTools ?? [];
|
||||
return [...tools].sort().join('\0');
|
||||
}
|
||||
|
||||
export function extensionConfigsMatch(sessionExt, desiredConfig) {
|
||||
if (extensionName(sessionExt) !== extensionName(desiredConfig)) return false;
|
||||
return extensionToolsKey(sessionExt) === extensionToolsKey(desiredConfig);
|
||||
}
|
||||
|
||||
export function extensionsNeedingRefresh(currentExtensions, desiredExtensions) {
|
||||
const current = currentExtensions ?? [];
|
||||
const desired = desiredExtensions ?? [];
|
||||
const toRemove = [];
|
||||
const toAdd = [];
|
||||
|
||||
for (const ext of current) {
|
||||
const name = extensionName(ext);
|
||||
if (!name) continue;
|
||||
const wanted = desired.find((item) => extensionName(item) === name);
|
||||
if (!wanted) {
|
||||
toRemove.push(name);
|
||||
continue;
|
||||
}
|
||||
if (!extensionConfigsMatch(ext, wanted)) {
|
||||
toRemove.push(name);
|
||||
toAdd.push(wanted);
|
||||
}
|
||||
}
|
||||
|
||||
for (const config of desired) {
|
||||
const name = extensionName(config);
|
||||
if (!name) continue;
|
||||
const exists = current.some((ext) => extensionName(ext) === name);
|
||||
if (!exists) {
|
||||
toAdd.push(config);
|
||||
}
|
||||
}
|
||||
|
||||
return { toRemove, toAdd };
|
||||
}
|
||||
|
||||
function samePath(left, right) {
|
||||
if (!left || !right) return false;
|
||||
return path.resolve(left) === path.resolve(right);
|
||||
}
|
||||
|
||||
async function readJson(upstream) {
|
||||
const text = await upstream.text();
|
||||
if (!upstream.ok) {
|
||||
throw new Error(text || `upstream ${upstream.status}`);
|
||||
}
|
||||
return text ? JSON.parse(text) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-apply H5 session policy after resume so stale extension sets cannot bypass capability limits.
|
||||
*/
|
||||
export async function reconcileAgentSession(
|
||||
apiFetch,
|
||||
sessionId,
|
||||
{ workingDir, sessionPolicy, sandboxConstraints = null },
|
||||
) {
|
||||
if (sessionPolicy?.unrestricted) return;
|
||||
|
||||
const session = await readJson(await apiFetch(`/sessions/${sessionId}`));
|
||||
if (!samePath(session?.working_dir, workingDir)) {
|
||||
await readJson(
|
||||
await apiFetch('/agent/update_working_dir', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ session_id: sessionId, working_dir: workingDir }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (sessionPolicy?.gooseMode) {
|
||||
await readJson(
|
||||
await apiFetch('/agent/update_session', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
goose_mode: sessionPolicy.gooseMode,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const desired = sessionPolicy?.extensionOverrides ?? [];
|
||||
const allowed = allowedExtensionNames(desired);
|
||||
const currentPayload = await readJson(await apiFetch(`/sessions/${sessionId}/extensions`));
|
||||
const current = currentPayload?.extensions ?? [];
|
||||
|
||||
for (const ext of current) {
|
||||
const name = extensionName(ext);
|
||||
if (!name || allowed.has(name)) continue;
|
||||
await readJson(
|
||||
await apiFetch('/agent/remove_extension', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ session_id: sessionId, name }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const refreshedPayload = await readJson(await apiFetch(`/sessions/${sessionId}/extensions`));
|
||||
const refreshed = refreshedPayload?.extensions ?? [];
|
||||
const { toRemove, toAdd } = extensionsNeedingRefresh(refreshed, desired);
|
||||
|
||||
for (const name of toRemove) {
|
||||
await readJson(
|
||||
await apiFetch('/agent/remove_extension', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ session_id: sessionId, name }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (const config of toAdd) {
|
||||
await readJson(
|
||||
await apiFetch('/agent/add_extension', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ session_id: sessionId, config }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await readJson(
|
||||
await apiFetch('/agent/restart', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ session_id: sessionId }),
|
||||
}),
|
||||
);
|
||||
|
||||
if (sandboxConstraints?.trim()) {
|
||||
const sandboxText = buildSandboxSessionConstraints({
|
||||
baseConstraints: sandboxConstraints,
|
||||
developerTools: developerToolsFromPolicy(sessionPolicy),
|
||||
});
|
||||
await apiFetch('/agent/harness_remember', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
sessionId,
|
||||
content: sandboxText,
|
||||
title: 'TKMind 用户空间沙箱',
|
||||
}),
|
||||
});
|
||||
await apiFetch('/agent/harness_bootstrap', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
sessionId,
|
||||
force: true,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user