mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:40:43 +00:00
fix(plugins): expose hook timeout overrides
This commit is contained in:
@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Gateway/usage: serve `usage.cost` and `sessions.usage` from a durable transcript aggregate cache with lock-safe background refreshes and localized stale-cache status, so large usage views avoid repeated full scans. (#76650) Thanks @Marvinthebored.
|
||||
- Plugins/hooks: let `plugins.entries.<id>.hooks.timeoutMs` and `plugins.entries.<id>.hooks.timeouts` bound plugin typed hooks from operator config, so slow hooks can be tuned without patching installed plugin code. Fixes #76778. Thanks @vincentkoc.
|
||||
- Telegram: add `channels.telegram.mediaGroupFlushMs` at the top level and per account so operators can tune album buffering instead of being stuck with the hard-coded 500ms media-group flush window. Fixes #76149. Thanks @vincentkoc.
|
||||
- Config/messages: coerce boolean `messages.visibleReplies` and `messages.groupChat.visibleReplies` values to the documented enum modes so an intuitive toggle no longer invalidates config and drops channel startup. Fixes #75390. Thanks @scottgl9.
|
||||
- Feishu: accept and honor `channels.feishu.blockStreaming` at the top level and per account, while keeping the legacy default off so Feishu cards no longer reject documented config or silently drop block replies. Fixes #75555. Thanks @vincentkoc.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
df881d10bfb3d1ba0439e5984117dde70b5f7e856696f25c7f4b5c978a38f841 config-baseline.json
|
||||
3a6c1626e7f5f6c7c8658516072e9ab327b668f6b25ecd3ab1e12cbcb6dc1f88 config-baseline.json
|
||||
f945a060012b3e7c675fb3ea0c5f18996cdcc06c9ec6cead389e04791a529ce9 config-baseline.core.json
|
||||
09a952cf734a5b4a30f760e570c0f106d54aa8e74bf439dd4d07013f9f7607e4 config-baseline.channel.json
|
||||
245aa98aabc6c2e3c57a69e639c2fb10d84a7e1e1b3bcdadc340fa61ca998287 config-baseline.plugin.json
|
||||
055fae0d0067a751dc10125af7421da45633f73519c94c982d02b0c4eb2bdf67 config-baseline.plugin.json
|
||||
|
||||
@@ -61,6 +61,32 @@ keep registration order.
|
||||
timeout. Omit it to use the default observation/decision timeout that the
|
||||
hook runner applies generically.
|
||||
|
||||
Operators can also set hook budgets without patching plugin code:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"my-plugin": {
|
||||
"hooks": {
|
||||
"timeoutMs": 30000,
|
||||
"timeouts": {
|
||||
"before_prompt_build": 90000,
|
||||
"agent_end": 60000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`hooks.timeouts.<hookName>` overrides `hooks.timeoutMs`, which overrides the
|
||||
plugin-authored `api.on(..., { timeoutMs })` value. Each configured value must
|
||||
be a positive integer no greater than 600000 milliseconds. Prefer per-hook
|
||||
overrides for known slow hooks so one plugin does not get a longer budget
|
||||
everywhere.
|
||||
|
||||
Each hook receives `event.context.pluginConfig`, the resolved config for the
|
||||
plugin that registered that handler. Use it for hook decisions that need
|
||||
current plugin options; OpenClaw injects it per handler without mutating the
|
||||
|
||||
@@ -374,6 +374,25 @@ describe("plugins.entries.*.hooks", () => {
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts bounded typed hook timeout overrides", () => {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-recall": {
|
||||
hooks: {
|
||||
timeoutMs: 30_000,
|
||||
timeouts: {
|
||||
before_prompt_build: 90_000,
|
||||
agent_end: 60_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects non-boolean values", () => {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
plugins: {
|
||||
@@ -405,6 +424,24 @@ describe("plugins.entries.*.hooks", () => {
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects invalid typed hook timeout overrides", () => {
|
||||
for (const hooks of [
|
||||
{ timeoutMs: 0 },
|
||||
{ timeoutMs: 600_001 },
|
||||
{ timeouts: { before_prompt_build: -1 } },
|
||||
{ timeouts: { before_prompt_build: 1.5 } },
|
||||
]) {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-recall": { hooks },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugins.entries.*.subagent", () => {
|
||||
|
||||
@@ -24091,6 +24091,28 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
"Controls whether this plugin may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, `before_agent_finalize`, and `agent_end`. Non-bundled plugins must opt in explicitly.",
|
||||
},
|
||||
timeoutMs: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 600000,
|
||||
title: "Plugin Hook Timeout (ms)",
|
||||
description:
|
||||
"Default timeout in milliseconds for this plugin's typed hooks, capped at 600000. Use this to bound slow plugin hooks without changing plugin code; per-hook values in hooks.timeouts take precedence.",
|
||||
},
|
||||
timeouts: {
|
||||
type: "object",
|
||||
propertyNames: {
|
||||
type: "string",
|
||||
},
|
||||
additionalProperties: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 600000,
|
||||
},
|
||||
title: "Plugin Hook Timeout Overrides",
|
||||
description:
|
||||
"Per-hook timeout overrides in milliseconds keyed by typed hook name, capped at 600000. Use narrow overrides for known slow hooks such as before_prompt_build or agent_end instead of raising every hook timeout.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Plugin Hook Policy",
|
||||
@@ -28867,6 +28889,16 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
tags: ["access"],
|
||||
},
|
||||
"plugins.entries.*.hooks.timeoutMs": {
|
||||
label: "Plugin Hook Timeout (ms)",
|
||||
help: "Default timeout in milliseconds for this plugin's typed hooks, capped at 600000. Use this to bound slow plugin hooks without changing plugin code; per-hook values in hooks.timeouts take precedence.",
|
||||
tags: ["performance"],
|
||||
},
|
||||
"plugins.entries.*.hooks.timeouts": {
|
||||
label: "Plugin Hook Timeout Overrides",
|
||||
help: "Per-hook timeout overrides in milliseconds keyed by typed hook name, capped at 600000. Use narrow overrides for known slow hooks such as before_prompt_build or agent_end instead of raising every hook timeout.",
|
||||
tags: ["performance"],
|
||||
},
|
||||
"plugins.entries.*.subagent": {
|
||||
label: "Plugin Subagent Policy",
|
||||
help: "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.",
|
||||
|
||||
@@ -369,6 +369,8 @@ const TARGET_KEYS = [
|
||||
"plugins.entries.*.hooks",
|
||||
"plugins.entries.*.hooks.allowPromptInjection",
|
||||
"plugins.entries.*.hooks.allowConversationAccess",
|
||||
"plugins.entries.*.hooks.timeoutMs",
|
||||
"plugins.entries.*.hooks.timeouts",
|
||||
"plugins.entries.*.subagent",
|
||||
"plugins.entries.*.subagent.allowModelOverride",
|
||||
"plugins.entries.*.subagent.allowedModels",
|
||||
@@ -800,6 +802,14 @@ describe("config help copy quality", () => {
|
||||
expect(pluginConversationPolicy.includes("llm_input")).toBe(true);
|
||||
expect(pluginConversationPolicy.includes("llm_output")).toBe(true);
|
||||
expect(pluginConversationPolicy.includes("before_agent_finalize")).toBe(true);
|
||||
|
||||
const pluginHookTimeout = FIELD_HELP["plugins.entries.*.hooks.timeoutMs"];
|
||||
expect(pluginHookTimeout.includes("typed hooks")).toBe(true);
|
||||
expect(pluginHookTimeout.includes("hooks.timeouts")).toBe(true);
|
||||
|
||||
const pluginHookTimeouts = FIELD_HELP["plugins.entries.*.hooks.timeouts"];
|
||||
expect(pluginHookTimeouts.includes("before_prompt_build")).toBe(true);
|
||||
expect(pluginHookTimeouts.includes("agent_end")).toBe(true);
|
||||
expect(pluginConversationPolicy.includes("agent_end")).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -1220,6 +1220,10 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
"plugins.entries.*.hooks.allowConversationAccess":
|
||||
"Controls whether this plugin may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, `before_agent_finalize`, and `agent_end`. Non-bundled plugins must opt in explicitly.",
|
||||
"plugins.entries.*.hooks.timeoutMs":
|
||||
"Default timeout in milliseconds for this plugin's typed hooks, capped at 600000. Use this to bound slow plugin hooks without changing plugin code; per-hook values in hooks.timeouts take precedence.",
|
||||
"plugins.entries.*.hooks.timeouts":
|
||||
"Per-hook timeout overrides in milliseconds keyed by typed hook name, capped at 600000. Use narrow overrides for known slow hooks such as before_prompt_build or agent_end instead of raising every hook timeout.",
|
||||
"plugins.entries.*.subagent":
|
||||
"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.",
|
||||
"plugins.entries.*.subagent.allowModelOverride":
|
||||
|
||||
@@ -915,6 +915,8 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"plugins.entries.*.hooks": "Plugin Hook Policy",
|
||||
"plugins.entries.*.hooks.allowConversationAccess": "Allow Conversation Access Hooks",
|
||||
"plugins.entries.*.hooks.allowPromptInjection": "Allow Prompt Injection Hooks",
|
||||
"plugins.entries.*.hooks.timeoutMs": "Plugin Hook Timeout (ms)",
|
||||
"plugins.entries.*.hooks.timeouts": "Plugin Hook Timeout Overrides",
|
||||
"plugins.entries.*.subagent": "Plugin Subagent Policy",
|
||||
"plugins.entries.*.subagent.allowModelOverride": "Allow Plugin Subagent Model Override",
|
||||
"plugins.entries.*.subagent.allowedModels": "Plugin Subagent Allowed Models",
|
||||
|
||||
@@ -8,6 +8,10 @@ export type PluginEntryConfig = {
|
||||
* Non-bundled plugins must opt in explicitly; bundled plugins stay allowed unless disabled.
|
||||
*/
|
||||
allowConversationAccess?: boolean;
|
||||
/** Default timeout in milliseconds for this plugin's typed hooks. */
|
||||
timeoutMs?: number;
|
||||
/** Per typed-hook timeout overrides in milliseconds. */
|
||||
timeouts?: Record<string, number>;
|
||||
};
|
||||
subagent?: {
|
||||
/** Explicitly allow this plugin to request per-run provider/model overrides for subagent runs. */
|
||||
|
||||
@@ -190,6 +190,8 @@ const PluginEntrySchema = z
|
||||
.object({
|
||||
allowPromptInjection: z.boolean().optional(),
|
||||
allowConversationAccess: z.boolean().optional(),
|
||||
timeoutMs: z.number().int().positive().max(600_000).optional(),
|
||||
timeouts: z.record(z.string(), z.number().int().positive().max(600_000)).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
||||
@@ -22,6 +22,8 @@ export type NormalizedPluginsConfig = {
|
||||
hooks?: {
|
||||
allowPromptInjection?: boolean;
|
||||
allowConversationAccess?: boolean;
|
||||
timeoutMs?: number;
|
||||
timeouts?: Record<string, number>;
|
||||
};
|
||||
subagent?: {
|
||||
allowModelOverride?: boolean;
|
||||
@@ -57,6 +59,33 @@ function normalizeSlotValue(value: unknown): string | null | undefined {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function normalizeHookTimeoutMs(value: unknown): number | undefined {
|
||||
if (
|
||||
typeof value !== "number" ||
|
||||
!Number.isInteger(value) ||
|
||||
!Number.isFinite(value) ||
|
||||
value <= 0 ||
|
||||
value > 600_000
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeHookTimeouts(value: unknown): Record<string, number> | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized: Record<string, number> = {};
|
||||
for (const [hookName, timeoutMs] of Object.entries(value)) {
|
||||
const normalizedTimeoutMs = normalizeHookTimeoutMs(timeoutMs);
|
||||
if (normalizedTimeoutMs !== undefined) {
|
||||
normalized[hookName] = normalizedTimeoutMs;
|
||||
}
|
||||
}
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizePluginEntries(
|
||||
entries: unknown,
|
||||
normalizePluginId: NormalizePluginId,
|
||||
@@ -83,12 +112,16 @@ function normalizePluginEntries(
|
||||
.allowPromptInjection,
|
||||
allowConversationAccess: (hooksRaw as { allowConversationAccess?: unknown })
|
||||
.allowConversationAccess,
|
||||
timeoutMs: normalizeHookTimeoutMs((hooksRaw as { timeoutMs?: unknown }).timeoutMs),
|
||||
timeouts: normalizeHookTimeouts((hooksRaw as { timeouts?: unknown }).timeouts),
|
||||
}
|
||||
: undefined;
|
||||
const normalizedHooks =
|
||||
hooks &&
|
||||
(typeof hooks.allowPromptInjection === "boolean" ||
|
||||
typeof hooks.allowConversationAccess === "boolean")
|
||||
typeof hooks.allowConversationAccess === "boolean" ||
|
||||
hooks.timeoutMs !== undefined ||
|
||||
hooks.timeouts !== undefined)
|
||||
? {
|
||||
...(typeof hooks.allowPromptInjection === "boolean"
|
||||
? { allowPromptInjection: hooks.allowPromptInjection }
|
||||
@@ -96,6 +129,8 @@ function normalizePluginEntries(
|
||||
...(typeof hooks.allowConversationAccess === "boolean"
|
||||
? { allowConversationAccess: hooks.allowConversationAccess }
|
||||
: {}),
|
||||
...(hooks.timeoutMs !== undefined ? { timeoutMs: hooks.timeoutMs } : {}),
|
||||
...(hooks.timeouts !== undefined ? { timeouts: hooks.timeouts } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const subagentRaw = entry.subagent;
|
||||
|
||||
@@ -71,11 +71,21 @@ describe("normalizePluginsConfig", () => {
|
||||
hooks: {
|
||||
allowPromptInjection: false,
|
||||
allowConversationAccess: true,
|
||||
timeoutMs: 250,
|
||||
timeouts: {
|
||||
before_prompt_build: 90_000,
|
||||
agent_end: 60_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHooks: {
|
||||
allowPromptInjection: false,
|
||||
allowConversationAccess: true,
|
||||
timeoutMs: 250,
|
||||
timeouts: {
|
||||
before_prompt_build: 90_000,
|
||||
agent_end: 60_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -84,6 +94,10 @@ describe("normalizePluginsConfig", () => {
|
||||
hooks: {
|
||||
allowPromptInjection: "nope",
|
||||
allowConversationAccess: "nope",
|
||||
timeoutMs: 0,
|
||||
timeouts: {
|
||||
before_prompt_build: 900_000,
|
||||
},
|
||||
} as unknown as { allowPromptInjection: boolean; allowConversationAccess: boolean },
|
||||
},
|
||||
expectedHooks: undefined,
|
||||
|
||||
@@ -290,7 +290,10 @@ function hasExplicitHookPolicyConfig(
|
||||
entry: NormalizedPluginsConfig["entries"][string] | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
entry?.hooks?.allowConversationAccess === true || entry?.hooks?.allowPromptInjection === true
|
||||
entry?.hooks?.allowConversationAccess === true ||
|
||||
entry?.hooks?.allowPromptInjection === true ||
|
||||
entry?.hooks?.timeoutMs !== undefined ||
|
||||
(entry?.hooks?.timeouts !== undefined && Object.keys(entry.hooks.timeouts).length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5387,6 +5387,44 @@ module.exports = {
|
||||
]);
|
||||
});
|
||||
|
||||
it("applies configured typed hook timeout overrides", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "hook-timeouts",
|
||||
filename: "hook-timeouts.cjs",
|
||||
body: `module.exports = { id: "hook-timeouts", register(api) {
|
||||
api.on("before_prompt_build", () => ({ prependContext: "prepend" }), { timeoutMs: 5000 });
|
||||
api.on("before_model_resolve", () => ({ providerOverride: "demo-provider" }));
|
||||
api.on("before_agent_start", () => ({ modelOverride: "demo-model" }));
|
||||
} };`,
|
||||
});
|
||||
|
||||
const registry = loadRegistryFromSinglePlugin({
|
||||
plugin,
|
||||
pluginConfig: {
|
||||
allow: ["hook-timeouts"],
|
||||
entries: {
|
||||
"hook-timeouts": {
|
||||
hooks: {
|
||||
timeoutMs: 250,
|
||||
timeouts: {
|
||||
before_model_resolve: 750,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.fromEntries(registry.typedHooks.map((entry) => [entry.hookName, entry.timeoutMs])),
|
||||
).toEqual({
|
||||
before_prompt_build: 250,
|
||||
before_model_resolve: 750,
|
||||
before_agent_start: 250,
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks conversation typed hooks for non-bundled plugins unless explicitly allowed", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
|
||||
@@ -224,8 +224,29 @@ export type {
|
||||
type PluginTypedHookPolicy = {
|
||||
allowPromptInjection?: boolean;
|
||||
allowConversationAccess?: boolean;
|
||||
timeoutMs?: number;
|
||||
timeouts?: Record<string, number>;
|
||||
};
|
||||
|
||||
function normalizeHookTimeoutMs(value: unknown): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function resolveTypedHookTimeoutMs(params: {
|
||||
hookName: PluginHookName;
|
||||
opts?: { timeoutMs?: number };
|
||||
policy?: PluginTypedHookPolicy;
|
||||
}): number | undefined {
|
||||
return (
|
||||
normalizeHookTimeoutMs(params.policy?.timeouts?.[params.hookName]) ??
|
||||
normalizeHookTimeoutMs(params.policy?.timeoutMs) ??
|
||||
normalizeHookTimeoutMs(params.opts?.timeoutMs)
|
||||
);
|
||||
}
|
||||
|
||||
const constrainLegacyPromptInjectionHook = (
|
||||
handler: PluginHookHandlerMap["before_agent_start"],
|
||||
): PluginHookHandlerMap["before_agent_start"] => {
|
||||
@@ -2047,13 +2068,14 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const timeoutMs = resolveTypedHookTimeoutMs({ hookName, opts, policy });
|
||||
record.hookCount += 1;
|
||||
registry.typedHooks.push({
|
||||
pluginId: record.id,
|
||||
hookName,
|
||||
handler: effectiveHandler,
|
||||
priority: opts?.priority,
|
||||
...(opts?.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
||||
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
||||
source: record.source,
|
||||
} as TypedPluginHookRegistration);
|
||||
};
|
||||
|
||||
@@ -575,6 +575,8 @@ describe("plugin status reports", () => {
|
||||
expectInspectPolicy(inspect!, {
|
||||
allowPromptInjection: undefined,
|
||||
allowConversationAccess: undefined,
|
||||
hookTimeoutMs: undefined,
|
||||
hookTimeouts: undefined,
|
||||
allowModelOverride: true,
|
||||
allowedModels: ["openai/gpt-5.5"],
|
||||
hasAllowedModelsConfig: true,
|
||||
@@ -733,6 +735,8 @@ describe("plugin status reports", () => {
|
||||
expectInspectPolicy(inspect!, {
|
||||
allowPromptInjection: false,
|
||||
allowConversationAccess: true,
|
||||
hookTimeoutMs: undefined,
|
||||
hookTimeouts: undefined,
|
||||
allowModelOverride: true,
|
||||
allowedModels: ["openai/gpt-5.5"],
|
||||
hasAllowedModelsConfig: true,
|
||||
|
||||
@@ -102,6 +102,8 @@ export type PluginInspectReport = {
|
||||
policy: {
|
||||
allowPromptInjection?: boolean;
|
||||
allowConversationAccess?: boolean;
|
||||
hookTimeoutMs?: number;
|
||||
hookTimeouts?: Record<string, number>;
|
||||
allowModelOverride?: boolean;
|
||||
allowedModels: string[];
|
||||
hasAllowedModelsConfig: boolean;
|
||||
@@ -515,6 +517,8 @@ export function buildPluginInspectReport(params: {
|
||||
policy: {
|
||||
allowPromptInjection: policyEntry?.hooks?.allowPromptInjection,
|
||||
allowConversationAccess: policyEntry?.hooks?.allowConversationAccess,
|
||||
hookTimeoutMs: policyEntry?.hooks?.timeoutMs,
|
||||
hookTimeouts: policyEntry?.hooks?.timeouts ? { ...policyEntry.hooks.timeouts } : undefined,
|
||||
allowModelOverride: policyEntry?.subagent?.allowModelOverride,
|
||||
allowedModels: [...(policyEntry?.subagent?.allowedModels ?? [])],
|
||||
hasAllowedModelsConfig: policyEntry?.subagent?.hasAllowedModelsConfig === true,
|
||||
|
||||
Reference in New Issue
Block a user