732 lines
21 KiB
TypeScript
732 lines
21 KiB
TypeScript
import { z } from "zod";
|
|
import { parseDurationMs } from "../cli/parse-duration.js";
|
|
import { AgentModelSchema } from "./zod-schema.agent-model.js";
|
|
import {
|
|
GroupChatSchema,
|
|
HumanDelaySchema,
|
|
IdentitySchema,
|
|
ToolsLinksSchema,
|
|
ToolsMediaSchema,
|
|
} from "./zod-schema.core.js";
|
|
import { sensitive } from "./zod-schema.sensitive.js";
|
|
|
|
export const HeartbeatSchema = z
|
|
.object({
|
|
every: z.string().optional(),
|
|
activeHours: z
|
|
.object({
|
|
start: z.string().optional(),
|
|
end: z.string().optional(),
|
|
timezone: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
model: z.string().optional(),
|
|
session: z.string().optional(),
|
|
includeReasoning: z.boolean().optional(),
|
|
target: z.string().optional(),
|
|
to: z.string().optional(),
|
|
accountId: z.string().optional(),
|
|
prompt: z.string().optional(),
|
|
ackMaxChars: z.number().int().nonnegative().optional(),
|
|
suppressToolErrorWarnings: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((val, ctx) => {
|
|
if (!val.every) {
|
|
return;
|
|
}
|
|
try {
|
|
parseDurationMs(val.every, { defaultUnit: "m" });
|
|
} catch {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["every"],
|
|
message: "invalid duration (use ms, s, m, h)",
|
|
});
|
|
}
|
|
|
|
const active = val.activeHours;
|
|
if (!active) {
|
|
return;
|
|
}
|
|
const timePattern = /^([01]\d|2[0-3]|24):([0-5]\d)$/;
|
|
const validateTime = (raw: string | undefined, opts: { allow24: boolean }, path: string) => {
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
if (!timePattern.test(raw)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["activeHours", path],
|
|
message: 'invalid time (use "HH:MM" 24h format)',
|
|
});
|
|
return;
|
|
}
|
|
const [hourStr, minuteStr] = raw.split(":");
|
|
const hour = Number(hourStr);
|
|
const minute = Number(minuteStr);
|
|
if (hour === 24 && minute !== 0) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["activeHours", path],
|
|
message: "invalid time (24:00 is the only allowed 24:xx value)",
|
|
});
|
|
return;
|
|
}
|
|
if (hour === 24 && !opts.allow24) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["activeHours", path],
|
|
message: "invalid time (start cannot be 24:00)",
|
|
});
|
|
}
|
|
};
|
|
|
|
validateTime(active.start, { allow24: false }, "start");
|
|
validateTime(active.end, { allow24: true }, "end");
|
|
})
|
|
.optional();
|
|
|
|
export const SandboxDockerSchema = z
|
|
.object({
|
|
image: z.string().optional(),
|
|
containerPrefix: z.string().optional(),
|
|
workdir: z.string().optional(),
|
|
readOnlyRoot: z.boolean().optional(),
|
|
tmpfs: z.array(z.string()).optional(),
|
|
network: z.string().optional(),
|
|
user: z.string().optional(),
|
|
capDrop: z.array(z.string()).optional(),
|
|
env: z.record(z.string(), z.string()).optional(),
|
|
setupCommand: z.string().optional(),
|
|
pidsLimit: z.number().int().positive().optional(),
|
|
memory: z.union([z.string(), z.number()]).optional(),
|
|
memorySwap: z.union([z.string(), z.number()]).optional(),
|
|
cpus: z.number().positive().optional(),
|
|
ulimits: z
|
|
.record(
|
|
z.string(),
|
|
z.union([
|
|
z.string(),
|
|
z.number(),
|
|
z
|
|
.object({
|
|
soft: z.number().int().nonnegative().optional(),
|
|
hard: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict(),
|
|
]),
|
|
)
|
|
.optional(),
|
|
seccompProfile: z.string().optional(),
|
|
apparmorProfile: z.string().optional(),
|
|
dns: z.array(z.string()).optional(),
|
|
extraHosts: z.array(z.string()).optional(),
|
|
binds: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((data, ctx) => {
|
|
if (data.binds) {
|
|
for (let i = 0; i < data.binds.length; i += 1) {
|
|
const bind = data.binds[i]?.trim() ?? "";
|
|
if (!bind) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["binds", i],
|
|
message: "Sandbox security: bind mount entry must be a non-empty string.",
|
|
});
|
|
continue;
|
|
}
|
|
const firstColon = bind.indexOf(":");
|
|
const source = (firstColon <= 0 ? bind : bind.slice(0, firstColon)).trim();
|
|
if (!source.startsWith("/")) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["binds", i],
|
|
message:
|
|
`Sandbox security: bind mount "${bind}" uses a non-absolute source path "${source}". ` +
|
|
"Only absolute POSIX paths are supported for sandbox binds.",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (data.network?.trim().toLowerCase() === "host") {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["network"],
|
|
message:
|
|
'Sandbox security: network mode "host" is blocked. Use "bridge" or "none" instead.',
|
|
});
|
|
}
|
|
if (data.seccompProfile?.trim().toLowerCase() === "unconfined") {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["seccompProfile"],
|
|
message:
|
|
'Sandbox security: seccomp profile "unconfined" is blocked. ' +
|
|
"Use a custom seccomp profile file or omit this setting.",
|
|
});
|
|
}
|
|
if (data.apparmorProfile?.trim().toLowerCase() === "unconfined") {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["apparmorProfile"],
|
|
message:
|
|
'Sandbox security: apparmor profile "unconfined" is blocked. ' +
|
|
"Use a named AppArmor profile or omit this setting.",
|
|
});
|
|
}
|
|
})
|
|
.optional();
|
|
|
|
export const SandboxBrowserSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
image: z.string().optional(),
|
|
containerPrefix: z.string().optional(),
|
|
network: z.string().optional(),
|
|
cdpPort: z.number().int().positive().optional(),
|
|
cdpSourceRange: z.string().optional(),
|
|
vncPort: z.number().int().positive().optional(),
|
|
noVncPort: z.number().int().positive().optional(),
|
|
headless: z.boolean().optional(),
|
|
enableNoVnc: z.boolean().optional(),
|
|
allowHostControl: z.boolean().optional(),
|
|
autoStart: z.boolean().optional(),
|
|
autoStartTimeoutMs: z.number().int().positive().optional(),
|
|
binds: z.array(z.string()).optional(),
|
|
})
|
|
.superRefine((data, ctx) => {
|
|
if (data.network?.trim().toLowerCase() === "host") {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["network"],
|
|
message:
|
|
'Sandbox security: browser network mode "host" is blocked. Use "bridge" or a custom bridge network instead.',
|
|
});
|
|
}
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
export const SandboxPruneSchema = z
|
|
.object({
|
|
idleHours: z.number().int().nonnegative().optional(),
|
|
maxAgeDays: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ToolPolicyBaseSchema = z
|
|
.object({
|
|
allow: z.array(z.string()).optional(),
|
|
alsoAllow: z.array(z.string()).optional(),
|
|
deny: z.array(z.string()).optional(),
|
|
})
|
|
.strict();
|
|
|
|
export const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) => {
|
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message:
|
|
"tools policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
|
});
|
|
}
|
|
}).optional();
|
|
|
|
export const ToolsWebSearchSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
provider: z.union([z.literal("brave"), z.literal("perplexity"), z.literal("grok")]).optional(),
|
|
apiKey: z.string().optional().register(sensitive),
|
|
maxResults: z.number().int().positive().optional(),
|
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
cacheTtlMinutes: z.number().nonnegative().optional(),
|
|
perplexity: z
|
|
.object({
|
|
apiKey: z.string().optional().register(sensitive),
|
|
baseUrl: z.string().optional(),
|
|
model: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
grok: z
|
|
.object({
|
|
apiKey: z.string().optional().register(sensitive),
|
|
model: z.string().optional(),
|
|
inlineCitations: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
export const ToolsWebFetchSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
maxChars: z.number().int().positive().optional(),
|
|
maxCharsCap: z.number().int().positive().optional(),
|
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
cacheTtlMinutes: z.number().nonnegative().optional(),
|
|
maxRedirects: z.number().int().nonnegative().optional(),
|
|
userAgent: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
export const ToolsWebSchema = z
|
|
.object({
|
|
search: ToolsWebSearchSchema,
|
|
fetch: ToolsWebFetchSchema,
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
export const ToolProfileSchema = z
|
|
.union([z.literal("minimal"), z.literal("coding"), z.literal("messaging"), z.literal("full")])
|
|
.optional();
|
|
|
|
type AllowlistPolicy = {
|
|
allow?: string[];
|
|
alsoAllow?: string[];
|
|
};
|
|
|
|
function addAllowAlsoAllowConflictIssue(
|
|
value: AllowlistPolicy,
|
|
ctx: z.RefinementCtx,
|
|
message: string,
|
|
): void {
|
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message,
|
|
});
|
|
}
|
|
}
|
|
|
|
export const ToolPolicyWithProfileSchema = z
|
|
.object({
|
|
allow: z.array(z.string()).optional(),
|
|
alsoAllow: z.array(z.string()).optional(),
|
|
deny: z.array(z.string()).optional(),
|
|
profile: ToolProfileSchema,
|
|
})
|
|
.strict()
|
|
.superRefine((value, ctx) => {
|
|
addAllowAlsoAllowConflictIssue(
|
|
value,
|
|
ctx,
|
|
"tools.byProvider policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
|
);
|
|
});
|
|
|
|
// Provider docking: allowlists keyed by provider id (no schema updates when adding providers).
|
|
export const ElevatedAllowFromSchema = z
|
|
.record(z.string(), z.array(z.union([z.string(), z.number()])))
|
|
.optional();
|
|
|
|
const ToolExecApplyPatchSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
workspaceOnly: z.boolean().optional(),
|
|
allowModels: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ToolExecSafeBinProfileSchema = z
|
|
.object({
|
|
minPositional: z.number().int().nonnegative().optional(),
|
|
maxPositional: z.number().int().nonnegative().optional(),
|
|
allowedValueFlags: z.array(z.string()).optional(),
|
|
deniedFlags: z.array(z.string()).optional(),
|
|
})
|
|
.strict();
|
|
|
|
const ToolExecBaseShape = {
|
|
host: z.enum(["sandbox", "gateway", "node"]).optional(),
|
|
security: z.enum(["deny", "allowlist", "full"]).optional(),
|
|
ask: z.enum(["off", "on-miss", "always"]).optional(),
|
|
node: z.string().optional(),
|
|
pathPrepend: z.array(z.string()).optional(),
|
|
safeBins: z.array(z.string()).optional(),
|
|
safeBinTrustedDirs: z.array(z.string()).optional(),
|
|
safeBinProfiles: z.record(z.string(), ToolExecSafeBinProfileSchema).optional(),
|
|
backgroundMs: z.number().int().positive().optional(),
|
|
timeoutSec: z.number().int().positive().optional(),
|
|
cleanupMs: z.number().int().positive().optional(),
|
|
notifyOnExit: z.boolean().optional(),
|
|
notifyOnExitEmptySuccess: z.boolean().optional(),
|
|
applyPatch: ToolExecApplyPatchSchema,
|
|
} as const;
|
|
|
|
const AgentToolExecSchema = z
|
|
.object({
|
|
...ToolExecBaseShape,
|
|
approvalRunningNoticeMs: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ToolExecSchema = z.object(ToolExecBaseShape).strict().optional();
|
|
|
|
const ToolFsSchema = z
|
|
.object({
|
|
workspaceOnly: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ToolLoopDetectionDetectorSchema = z
|
|
.object({
|
|
genericRepeat: z.boolean().optional(),
|
|
knownPollNoProgress: z.boolean().optional(),
|
|
pingPong: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ToolLoopDetectionSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
historySize: z.number().int().positive().optional(),
|
|
warningThreshold: z.number().int().positive().optional(),
|
|
criticalThreshold: z.number().int().positive().optional(),
|
|
globalCircuitBreakerThreshold: z.number().int().positive().optional(),
|
|
detectors: ToolLoopDetectionDetectorSchema,
|
|
})
|
|
.strict()
|
|
.superRefine((value, ctx) => {
|
|
if (
|
|
value.warningThreshold !== undefined &&
|
|
value.criticalThreshold !== undefined &&
|
|
value.warningThreshold >= value.criticalThreshold
|
|
) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["criticalThreshold"],
|
|
message: "tools.loopDetection.warningThreshold must be lower than criticalThreshold.",
|
|
});
|
|
}
|
|
if (
|
|
value.criticalThreshold !== undefined &&
|
|
value.globalCircuitBreakerThreshold !== undefined &&
|
|
value.criticalThreshold >= value.globalCircuitBreakerThreshold
|
|
) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["globalCircuitBreakerThreshold"],
|
|
message:
|
|
"tools.loopDetection.criticalThreshold must be lower than globalCircuitBreakerThreshold.",
|
|
});
|
|
}
|
|
})
|
|
.optional();
|
|
|
|
export const AgentSandboxSchema = z
|
|
.object({
|
|
mode: z.union([z.literal("off"), z.literal("non-main"), z.literal("all")]).optional(),
|
|
workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
|
|
sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
|
|
scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
|
|
perSession: z.boolean().optional(),
|
|
workspaceRoot: z.string().optional(),
|
|
docker: SandboxDockerSchema,
|
|
browser: SandboxBrowserSchema,
|
|
prune: SandboxPruneSchema,
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const CommonToolPolicyFields = {
|
|
profile: ToolProfileSchema,
|
|
allow: z.array(z.string()).optional(),
|
|
alsoAllow: z.array(z.string()).optional(),
|
|
deny: z.array(z.string()).optional(),
|
|
byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(),
|
|
};
|
|
|
|
export const AgentToolsSchema = z
|
|
.object({
|
|
...CommonToolPolicyFields,
|
|
elevated: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
allowFrom: ElevatedAllowFromSchema,
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
exec: AgentToolExecSchema,
|
|
fs: ToolFsSchema,
|
|
loopDetection: ToolLoopDetectionSchema,
|
|
sandbox: z
|
|
.object({
|
|
tools: ToolPolicySchema,
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((value, ctx) => {
|
|
addAllowAlsoAllowConflictIssue(
|
|
value,
|
|
ctx,
|
|
"agent tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
|
);
|
|
})
|
|
.optional();
|
|
|
|
export const MemorySearchSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
sources: z.array(z.union([z.literal("memory"), z.literal("sessions")])).optional(),
|
|
extraPaths: z.array(z.string()).optional(),
|
|
experimental: z
|
|
.object({
|
|
sessionMemory: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
provider: z
|
|
.union([
|
|
z.literal("openai"),
|
|
z.literal("local"),
|
|
z.literal("gemini"),
|
|
z.literal("voyage"),
|
|
z.literal("mistral"),
|
|
])
|
|
.optional(),
|
|
remote: z
|
|
.object({
|
|
baseUrl: z.string().optional(),
|
|
apiKey: z.string().optional().register(sensitive),
|
|
headers: z.record(z.string(), z.string()).optional(),
|
|
batch: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
wait: z.boolean().optional(),
|
|
concurrency: z.number().int().positive().optional(),
|
|
pollIntervalMs: z.number().int().nonnegative().optional(),
|
|
timeoutMinutes: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
fallback: z
|
|
.union([
|
|
z.literal("openai"),
|
|
z.literal("gemini"),
|
|
z.literal("local"),
|
|
z.literal("voyage"),
|
|
z.literal("mistral"),
|
|
z.literal("none"),
|
|
])
|
|
.optional(),
|
|
model: z.string().optional(),
|
|
local: z
|
|
.object({
|
|
modelPath: z.string().optional(),
|
|
modelCacheDir: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
store: z
|
|
.object({
|
|
driver: z.literal("sqlite").optional(),
|
|
path: z.string().optional(),
|
|
vector: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
extensionPath: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
chunking: z
|
|
.object({
|
|
tokens: z.number().int().positive().optional(),
|
|
overlap: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
sync: z
|
|
.object({
|
|
onSessionStart: z.boolean().optional(),
|
|
onSearch: z.boolean().optional(),
|
|
watch: z.boolean().optional(),
|
|
watchDebounceMs: z.number().int().nonnegative().optional(),
|
|
intervalMinutes: z.number().int().nonnegative().optional(),
|
|
sessions: z
|
|
.object({
|
|
deltaBytes: z.number().int().nonnegative().optional(),
|
|
deltaMessages: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
query: z
|
|
.object({
|
|
maxResults: z.number().int().positive().optional(),
|
|
minScore: z.number().min(0).max(1).optional(),
|
|
hybrid: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
vectorWeight: z.number().min(0).max(1).optional(),
|
|
textWeight: z.number().min(0).max(1).optional(),
|
|
candidateMultiplier: z.number().int().positive().optional(),
|
|
mmr: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
lambda: z.number().min(0).max(1).optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
temporalDecay: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
halfLifeDays: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
cache: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
maxEntries: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
export { AgentModelSchema };
|
|
export const AgentEntrySchema = z
|
|
.object({
|
|
id: z.string(),
|
|
default: z.boolean().optional(),
|
|
name: z.string().optional(),
|
|
workspace: z.string().optional(),
|
|
agentDir: z.string().optional(),
|
|
model: AgentModelSchema.optional(),
|
|
skills: z.array(z.string()).optional(),
|
|
memorySearch: MemorySearchSchema,
|
|
humanDelay: HumanDelaySchema.optional(),
|
|
heartbeat: HeartbeatSchema,
|
|
identity: IdentitySchema,
|
|
groupChat: GroupChatSchema,
|
|
subagents: z
|
|
.object({
|
|
allowAgents: z.array(z.string()).optional(),
|
|
model: z
|
|
.union([
|
|
z.string(),
|
|
z
|
|
.object({
|
|
primary: z.string().optional(),
|
|
fallbacks: z.array(z.string()).optional(),
|
|
})
|
|
.strict(),
|
|
])
|
|
.optional(),
|
|
thinking: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
sandbox: AgentSandboxSchema,
|
|
tools: AgentToolsSchema,
|
|
})
|
|
.strict();
|
|
|
|
export const ToolsSchema = z
|
|
.object({
|
|
...CommonToolPolicyFields,
|
|
web: ToolsWebSchema,
|
|
media: ToolsMediaSchema,
|
|
links: ToolsLinksSchema,
|
|
sessions: z
|
|
.object({
|
|
visibility: z.enum(["self", "tree", "agent", "all"]).optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
loopDetection: ToolLoopDetectionSchema,
|
|
message: z
|
|
.object({
|
|
allowCrossContextSend: z.boolean().optional(),
|
|
crossContext: z
|
|
.object({
|
|
allowWithinProvider: z.boolean().optional(),
|
|
allowAcrossProviders: z.boolean().optional(),
|
|
marker: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
prefix: z.string().optional(),
|
|
suffix: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
broadcast: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
agentToAgent: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
allow: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
elevated: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
allowFrom: ElevatedAllowFromSchema,
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
exec: ToolExecSchema,
|
|
fs: ToolFsSchema,
|
|
subagents: z
|
|
.object({
|
|
tools: ToolPolicySchema,
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
sandbox: z
|
|
.object({
|
|
tools: ToolPolicySchema,
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((value, ctx) => {
|
|
addAllowAlsoAllowConflictIssue(
|
|
value,
|
|
ctx,
|
|
"tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
|
);
|
|
})
|
|
.optional();
|