feat: make agent roles and skills dynamic

This commit is contained in:
2026-07-11 12:55:05 +08:00
parent 17c0536958
commit d36b00bbec
48 changed files with 1389 additions and 1749 deletions
+18 -6
View File
@@ -1,7 +1,8 @@
import { chmod, lstat, mkdir, realpath } from "node:fs/promises";
import { homedir } from "node:os";
import { isAbsolute, join, relative, resolve } from "node:path";
import { validateCuratedSkillPlugin } from "./curatedSkills.js";
import type { RoleSkillEntry } from "./models.js";
import { prepareRunSkillPlugin, readSkillStoreRoot } from "./skillStore.js";
const PROVIDER_ENV_KEYS = new Set([
"ANTHROPIC_BASE_URL",
@@ -33,8 +34,10 @@ const SANDBOX_HIDDEN_ENV_KEYS = [
const MAX_AGENT_TMP_PREFIX_BYTES = 56;
export interface AgentSecurityInput {
readonly runId: string;
readonly workspaceRoot: string;
readonly workspaceDir: string;
readonly skills?: readonly RoleSkillEntry[] | undefined;
/** Run-scoped loopback proxy capability; customer provider secrets are forbidden here. */
readonly providerProxyEnv?: Readonly<Record<string, string | undefined>> | undefined;
readonly hostEnv?: Readonly<Record<string, string | undefined>> | undefined;
@@ -62,7 +65,8 @@ export interface AgentSecurityPolicy {
readonly workspaceRoot: string;
readonly env: Record<string, string | undefined>;
readonly skillIds: readonly string[];
readonly skillPluginRoot: string;
readonly skillPluginRoot?: string | undefined;
cleanup(): Promise<void>;
readonly sandbox: AgentSandboxPolicy;
}
@@ -82,7 +86,6 @@ export async function createAgentSecurityPolicy(input: AgentSecurityInput): Prom
const agentState = await ensureDirectoryTree(runtimeRoot, ["state"]);
const agentTmp = await ensureDirectoryTree(cphRoot, ["t"]);
assertShortAgentTemp(agentTmp);
const skillPlugin = await validateCuratedSkillPlugin();
const path = hostEnv["PATH"]?.trim();
if (path === undefined || path === "") {
@@ -119,12 +122,21 @@ export async function createAgentSecurityPolicy(input: AgentSecurityInput): Prom
const sensitiveReadPaths = hostSensitiveReadPaths(hostEnv);
const runtimeReadPaths = hostRuntimeReadPaths(hostEnv);
const selectedSkills = input.skills ?? [];
const skillPlugin = selectedSkills.length === 0
? null
: await prepareRunSkillPlugin({
storeRoot: readSkillStoreRoot(hostEnv),
runId: input.runId,
skills: selectedSkills,
});
return {
cwd: workspaceDir,
workspaceRoot,
env,
skillIds: skillPlugin.skillIds,
skillPluginRoot: skillPlugin.root,
skillIds: skillPlugin?.skillIds ?? [],
...(skillPlugin !== null ? { skillPluginRoot: skillPlugin.root } : {}),
cleanup: skillPlugin?.cleanup ?? (async () => {}),
sandbox: {
enabled: true,
failIfUnavailable: true,
@@ -140,7 +152,7 @@ export async function createAgentSecurityPolicy(input: AgentSecurityInput): Prom
// workspace plus the named system runtime needed to execute tools.
// SDK allowRead takes precedence over matching denyRead paths.
denyRead: ["/"],
allowRead: [workspaceDir, skillPlugin.root, ...runtimeReadPaths],
allowRead: [workspaceDir, ...(skillPlugin !== null ? [skillPlugin.root] : []), ...runtimeReadPaths],
},
credentials: {
files: sensitiveReadPaths.map((path) => ({ path, mode: "deny" as const })),