4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
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,
|
|
}),
|
|
});
|
|
}
|
|
}
|