feat: add memindadm runtime policy for agent code runs (Phase 1.5)

Persist code-run gates in admin DB and expose them via /auth/status so H5 can honor runtime policy without VITE rebuilds.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-23 21:33:27 +08:00
parent 13f5c740a1
commit 239c41f935
12 changed files with 639 additions and 43 deletions
+2
View File
@@ -42,6 +42,7 @@ import type {
} from '../types';
import { normalizeConversationMessages, normalizeUserMessageForApi } from '../utils/message';
import type { AgentRunCreateOptions, AgentRunValidation } from '../utils/agentRunMode';
import { applyAgentCodeRunClientPolicy } from '../utils/agentRunMode';
import {
API,
ApiError,
@@ -319,6 +320,7 @@ export async function checkAuth(): Promise<AuthStatus> {
return { authenticated: false };
}
if (status.authenticated) resetUnauthorizedGuard();
applyAgentCodeRunClientPolicy(status.agentCodeRun ?? null);
if (status.authenticated && status.mode === 'user' && !status.capabilities) {
try {
const me = await getMe();
+9
View File
@@ -905,6 +905,14 @@ export type PolicyDefinition = {
risk: 'low' | 'medium' | 'high';
};
export type AgentCodeRunClientPolicy = {
codeRun?: {
enabled?: boolean;
pageDataDevAutodetect?: boolean;
generalAutodetect?: boolean;
};
};
export type AuthStatus = {
authenticated: boolean;
mode?: 'user' | 'legacy' | 'none' | 'unavailable';
@@ -913,6 +921,7 @@ export type AuthStatus = {
capabilities?: CapabilityMap;
grantedSkills?: string[];
unrestricted?: boolean;
agentCodeRun?: AgentCodeRunClientPolicy | null;
};
export type PathGrant = {
+32 -5
View File
@@ -1,4 +1,4 @@
import type { MindSpaceChatContext } from '../types';
import type { MindSpaceChatContext, AgentCodeRunClientPolicy } from '../types';
export type AgentRunCreateOptions = {
toolMode?: 'chat' | 'code';
@@ -32,6 +32,33 @@ export const agentPageDataDevAutodetectEnabled = envFlag(
import.meta.env.VITE_AGENT_PAGE_DATA_DEV_AUTODETECT,
);
let runtimeAgentCodeRunPolicy: AgentCodeRunClientPolicy | null = null;
export function applyAgentCodeRunClientPolicy(policy: AgentCodeRunClientPolicy | null) {
runtimeAgentCodeRunPolicy = policy;
}
function clientCodeRunsEnabled(): boolean {
if (runtimeAgentCodeRunPolicy?.codeRun?.enabled != null) {
return runtimeAgentCodeRunPolicy.codeRun.enabled;
}
return agentCodeRunsEnabled;
}
function clientCodeRunsAutodetectEnabled(): boolean {
if (runtimeAgentCodeRunPolicy?.codeRun?.generalAutodetect != null) {
return runtimeAgentCodeRunPolicy.codeRun.generalAutodetect;
}
return agentCodeRunsAutodetectEnabled;
}
function clientPageDataDevAutodetectEnabled(): boolean {
if (runtimeAgentCodeRunPolicy?.codeRun?.pageDataDevAutodetect != null) {
return runtimeAgentCodeRunPolicy.codeRun.pageDataDevAutodetect;
}
return agentPageDataDevAutodetectEnabled;
}
export const PAGE_DATA_DEV_TASK_TYPE = 'page_data_dev';
function parseUserIdSet(value: unknown): Set<string> {
@@ -46,7 +73,7 @@ function parseUserIdSet(value: unknown): Set<string> {
const agentCodeRunUserIds = parseUserIdSet(import.meta.env.VITE_AGENT_CODE_RUNS_USER_IDS);
export function agentCodeRunsEnabledForUser(userId?: string | null): boolean {
if (!agentCodeRunsEnabled) return false;
if (!clientCodeRunsEnabled()) return false;
if (agentCodeRunUserIds.size === 0) return true;
return Boolean(userId && agentCodeRunUserIds.has(userId));
}
@@ -68,7 +95,7 @@ export function isPageDataDevTaskText(text: string): boolean {
}
export function resolvePageDataDevTaskType(text: string): string | null {
if (!agentPageDataDevAutodetectEnabled) return null;
if (!clientPageDataDevAutodetectEnabled()) return null;
return isPageDataDevTaskText(text) ? PAGE_DATA_DEV_TASK_TYPE : null;
}
@@ -263,8 +290,8 @@ export function resolveAgentRunOptions(
{
taskType = 'code_task',
forceCode = false,
allowAutodetect = agentCodeRunsAutodetectEnabled,
allowPageDataDevAutodetect = agentPageDataDevAutodetectEnabled,
allowAutodetect = clientCodeRunsAutodetectEnabled(),
allowPageDataDevAutodetect = clientPageDataDevAutodetectEnabled(),
userId = null,
requestId = null,
mindspaceContext = null,