mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(web): add configurable cron command execution settings (#1647)
- add tools.cron.allow_command config with a default value of true - require command_confirm only when cron command execution is disabled - expose cron command permission and timeout settings in the config UI - add backend tests and update i18n strings
This commit is contained in:
@@ -699,8 +699,9 @@ type WebToolsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CronToolsConfig struct {
|
type CronToolsConfig struct {
|
||||||
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_CRON_"`
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_CRON_"`
|
||||||
ExecTimeoutMinutes int ` env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES" json:"exec_timeout_minutes"` // 0 means no timeout
|
ExecTimeoutMinutes int ` env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES" json:"exec_timeout_minutes"` // 0 means no timeout
|
||||||
|
AllowCommand bool ` env:"PICOCLAW_TOOLS_CRON_ALLOW_COMMAND" json:"allow_command"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecConfig struct {
|
type ExecConfig struct {
|
||||||
|
|||||||
@@ -405,6 +405,13 @@ func TestDefaultConfig_ExecAllowRemoteEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultConfig_CronAllowCommandEnabled(t *testing.T) {
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
if !cfg.Tools.Cron.AllowCommand {
|
||||||
|
t.Fatal("DefaultConfig().Tools.Cron.AllowCommand should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
@@ -437,6 +444,22 @@ func TestLoadConfig_ExecAllowRemoteDefaultsTrueWhenUnset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig_CronAllowCommandDefaultsTrueWhenUnset(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "config.json")
|
||||||
|
if err := os.WriteFile(configPath, []byte(`{"tools":{"cron":{"exec_timeout_minutes":5}}}`), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error: %v", err)
|
||||||
|
}
|
||||||
|
if !cfg.Tools.Cron.AllowCommand {
|
||||||
|
t.Fatal("tools.cron.allow_command should remain true when unset in config file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ func DefaultConfig() *Config {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
ExecTimeoutMinutes: 5,
|
ExecTimeoutMinutes: 5,
|
||||||
|
AllowCommand: true,
|
||||||
},
|
},
|
||||||
Exec: ExecConfig{
|
Exec: ExecConfig{
|
||||||
ToolConfig: ToolConfig{
|
ToolConfig: ToolConfig{
|
||||||
|
|||||||
+21
-13
@@ -20,10 +20,11 @@ type JobExecutor interface {
|
|||||||
|
|
||||||
// CronTool provides scheduling capabilities for the agent
|
// CronTool provides scheduling capabilities for the agent
|
||||||
type CronTool struct {
|
type CronTool struct {
|
||||||
cronService *cron.CronService
|
cronService *cron.CronService
|
||||||
executor JobExecutor
|
executor JobExecutor
|
||||||
msgBus *bus.MessageBus
|
msgBus *bus.MessageBus
|
||||||
execTool *ExecTool
|
execTool *ExecTool
|
||||||
|
allowCommand bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCronTool creates a new CronTool
|
// NewCronTool creates a new CronTool
|
||||||
@@ -37,12 +38,18 @@ func NewCronTool(
|
|||||||
return nil, fmt.Errorf("unable to configure exec tool: %w", err)
|
return nil, fmt.Errorf("unable to configure exec tool: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowCommand := true
|
||||||
|
if config != nil {
|
||||||
|
allowCommand = config.Tools.Cron.AllowCommand
|
||||||
|
}
|
||||||
|
|
||||||
execTool.SetTimeout(execTimeout)
|
execTool.SetTimeout(execTimeout)
|
||||||
return &CronTool{
|
return &CronTool{
|
||||||
cronService: cronService,
|
cronService: cronService,
|
||||||
executor: executor,
|
executor: executor,
|
||||||
msgBus: msgBus,
|
msgBus: msgBus,
|
||||||
execTool: execTool,
|
execTool: execTool,
|
||||||
|
allowCommand: allowCommand,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +83,7 @@ func (t *CronTool) Parameters() map[string]any {
|
|||||||
},
|
},
|
||||||
"command_confirm": map[string]any{
|
"command_confirm": map[string]any{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Required when using command=true. Must be true to explicitly confirm scheduling a shell command.",
|
"description": "Optional explicit confirmation flag for scheduling a shell command. Command execution must also be enabled via tools.cron.allow_command.",
|
||||||
},
|
},
|
||||||
"at_seconds": map[string]any{
|
"at_seconds": map[string]any{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -180,16 +187,17 @@ func (t *CronTool) addJob(ctx context.Context, args map[string]any) *ToolResult
|
|||||||
deliver = d
|
deliver = d
|
||||||
}
|
}
|
||||||
|
|
||||||
// GHSA-pv8c-p6jf-3fpp: command scheduling requires internal channel + explicit confirm.
|
// GHSA-pv8c-p6jf-3fpp: command scheduling requires internal channel. When
|
||||||
// Non-command reminders (plain messages) remain open to all channels.
|
// allow_command is disabled, explicit confirmation is required as an override.
|
||||||
|
// Non-command reminders remain open to all channels.
|
||||||
command, _ := args["command"].(string)
|
command, _ := args["command"].(string)
|
||||||
commandConfirm, _ := args["command_confirm"].(bool)
|
commandConfirm, _ := args["command_confirm"].(bool)
|
||||||
if command != "" {
|
if command != "" {
|
||||||
if !constants.IsInternalChannel(channel) {
|
if !constants.IsInternalChannel(channel) {
|
||||||
return ErrorResult("scheduling command execution is restricted to internal channels")
|
return ErrorResult("scheduling command execution is restricted to internal channels")
|
||||||
}
|
}
|
||||||
if !commandConfirm {
|
if !t.allowCommand && !commandConfirm {
|
||||||
return ErrorResult("command_confirm=true is required to schedule command execution")
|
return ErrorResult("command_confirm=true is required when allow_command is disabled")
|
||||||
}
|
}
|
||||||
deliver = false
|
deliver = false
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-6
@@ -11,12 +11,11 @@ import (
|
|||||||
"github.com/sipeed/picoclaw/pkg/cron"
|
"github.com/sipeed/picoclaw/pkg/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestCronTool(t *testing.T) *CronTool {
|
func newTestCronToolWithConfig(t *testing.T, cfg *config.Config) *CronTool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
storePath := filepath.Join(t.TempDir(), "cron.json")
|
storePath := filepath.Join(t.TempDir(), "cron.json")
|
||||||
cronService := cron.NewCronService(storePath, nil)
|
cronService := cron.NewCronService(storePath, nil)
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
cfg := config.DefaultConfig()
|
|
||||||
tool, err := NewCronTool(cronService, nil, msgBus, t.TempDir(), true, 0, cfg)
|
tool, err := NewCronTool(cronService, nil, msgBus, t.TempDir(), true, 0, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewCronTool() error: %v", err)
|
t.Fatalf("NewCronTool() error: %v", err)
|
||||||
@@ -24,6 +23,11 @@ func newTestCronTool(t *testing.T) *CronTool {
|
|||||||
return tool
|
return tool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newTestCronTool(t *testing.T) *CronTool {
|
||||||
|
t.Helper()
|
||||||
|
return newTestCronToolWithConfig(t, config.DefaultConfig())
|
||||||
|
}
|
||||||
|
|
||||||
// TestCronTool_CommandBlockedFromRemoteChannel verifies command scheduling is restricted to internal channels
|
// TestCronTool_CommandBlockedFromRemoteChannel verifies command scheduling is restricted to internal channels
|
||||||
func TestCronTool_CommandBlockedFromRemoteChannel(t *testing.T) {
|
func TestCronTool_CommandBlockedFromRemoteChannel(t *testing.T) {
|
||||||
tool := newTestCronTool(t)
|
tool := newTestCronTool(t)
|
||||||
@@ -44,8 +48,7 @@ func TestCronTool_CommandBlockedFromRemoteChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCronTool_CommandRequiresConfirm verifies command_confirm=true is required
|
func TestCronTool_CommandDoesNotRequireConfirmByDefault(t *testing.T) {
|
||||||
func TestCronTool_CommandRequiresConfirm(t *testing.T) {
|
|
||||||
tool := newTestCronTool(t)
|
tool := newTestCronTool(t)
|
||||||
ctx := WithToolContext(context.Background(), "cli", "direct")
|
ctx := WithToolContext(context.Background(), "cli", "direct")
|
||||||
result := tool.Execute(ctx, map[string]any{
|
result := tool.Execute(ctx, map[string]any{
|
||||||
@@ -55,11 +58,57 @@ func TestCronTool_CommandRequiresConfirm(t *testing.T) {
|
|||||||
"at_seconds": float64(60),
|
"at_seconds": float64(60),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if result.IsError {
|
||||||
|
t.Fatalf("expected command scheduling without confirm to succeed by default, got: %s", result.ForLLM)
|
||||||
|
}
|
||||||
|
if !strings.Contains(result.ForLLM, "Cron job added") {
|
||||||
|
t.Errorf("expected 'Cron job added', got: %s", result.ForLLM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronTool_CommandRequiresConfirmWhenAllowCommandDisabled(t *testing.T) {
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Tools.Cron.AllowCommand = false
|
||||||
|
|
||||||
|
tool := newTestCronToolWithConfig(t, cfg)
|
||||||
|
ctx := WithToolContext(context.Background(), "cli", "direct")
|
||||||
|
result := tool.Execute(ctx, map[string]any{
|
||||||
|
"action": "add",
|
||||||
|
"message": "check disk",
|
||||||
|
"command": "df -h",
|
||||||
|
"at_seconds": float64(60),
|
||||||
|
})
|
||||||
|
|
||||||
if !result.IsError {
|
if !result.IsError {
|
||||||
t.Fatal("expected error when command_confirm is missing")
|
t.Fatal("expected command scheduling to require confirm when allow_command is disabled")
|
||||||
}
|
}
|
||||||
if !strings.Contains(result.ForLLM, "command_confirm=true") {
|
if !strings.Contains(result.ForLLM, "command_confirm=true") {
|
||||||
t.Errorf("expected 'command_confirm=true' message, got: %s", result.ForLLM)
|
t.Errorf("expected command_confirm requirement message, got: %s", result.ForLLM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronTool_CommandAllowedWithConfirmWhenAllowCommandDisabled(t *testing.T) {
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Tools.Cron.AllowCommand = false
|
||||||
|
|
||||||
|
tool := newTestCronToolWithConfig(t, cfg)
|
||||||
|
ctx := WithToolContext(context.Background(), "cli", "direct")
|
||||||
|
result := tool.Execute(ctx, map[string]any{
|
||||||
|
"action": "add",
|
||||||
|
"message": "check disk",
|
||||||
|
"command": "df -h",
|
||||||
|
"command_confirm": true,
|
||||||
|
"at_seconds": float64(60),
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.IsError {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected command scheduling with confirm to succeed when allow_command is disabled, got: %s",
|
||||||
|
result.ForLLM,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !strings.Contains(result.ForLLM, "Cron job added") {
|
||||||
|
t.Errorf("expected 'Cron job added', got: %s", result.ForLLM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "@/api/system"
|
} from "@/api/system"
|
||||||
import {
|
import {
|
||||||
AgentDefaultsSection,
|
AgentDefaultsSection,
|
||||||
|
CronSection,
|
||||||
DevicesSection,
|
DevicesSection,
|
||||||
LauncherSection,
|
LauncherSection,
|
||||||
RuntimeSection,
|
RuntimeSection,
|
||||||
@@ -164,6 +165,11 @@ export function ConfigPage() {
|
|||||||
"Heartbeat interval",
|
"Heartbeat interval",
|
||||||
{ min: 1 },
|
{ min: 1 },
|
||||||
)
|
)
|
||||||
|
const cronExecTimeoutMinutes = parseIntField(
|
||||||
|
form.cronExecTimeoutMinutes,
|
||||||
|
"Cron exec timeout",
|
||||||
|
{ min: 0 },
|
||||||
|
)
|
||||||
|
|
||||||
await patchAppConfig({
|
await patchAppConfig({
|
||||||
agents: {
|
agents: {
|
||||||
@@ -180,6 +186,10 @@ export function ConfigPage() {
|
|||||||
dm_scope: dmScope,
|
dm_scope: dmScope,
|
||||||
},
|
},
|
||||||
tools: {
|
tools: {
|
||||||
|
cron: {
|
||||||
|
allow_command: form.allowCommand,
|
||||||
|
exec_timeout_minutes: cronExecTimeoutMinutes,
|
||||||
|
},
|
||||||
exec: {
|
exec: {
|
||||||
allow_remote: form.allowRemote,
|
allow_remote: form.allowRemote,
|
||||||
},
|
},
|
||||||
@@ -279,6 +289,8 @@ export function ConfigPage() {
|
|||||||
|
|
||||||
<RuntimeSection form={form} onFieldChange={updateField} />
|
<RuntimeSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
|
<CronSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<LauncherSection
|
<LauncherSection
|
||||||
launcherForm={launcherForm}
|
launcherForm={launcherForm}
|
||||||
onFieldChange={updateLauncherField}
|
onFieldChange={updateLauncherField}
|
||||||
|
|||||||
@@ -236,6 +236,42 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CronSectionProps {
|
||||||
|
form: CoreConfigForm
|
||||||
|
onFieldChange: UpdateCoreField
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CronSection({ form, onFieldChange }: CronSectionProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigSectionCard title={t("pages.config.sections.cron")}>
|
||||||
|
<SwitchCardField
|
||||||
|
label={t("pages.config.allow_shell_execution")}
|
||||||
|
hint={t("pages.config.allow_shell_execution_hint")}
|
||||||
|
layout="setting-row"
|
||||||
|
checked={form.allowCommand}
|
||||||
|
onCheckedChange={(checked) => onFieldChange("allowCommand", checked)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
label={t("pages.config.cron_exec_timeout")}
|
||||||
|
hint={t("pages.config.cron_exec_timeout_hint")}
|
||||||
|
layout="setting-row"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
value={form.cronExecTimeoutMinutes}
|
||||||
|
onChange={(e) =>
|
||||||
|
onFieldChange("cronExecTimeoutMinutes", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</ConfigSectionCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface LauncherSectionProps {
|
interface LauncherSectionProps {
|
||||||
launcherForm: LauncherForm
|
launcherForm: LauncherForm
|
||||||
onFieldChange: UpdateLauncherField
|
onFieldChange: UpdateLauncherField
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export interface CoreConfigForm {
|
|||||||
workspace: string
|
workspace: string
|
||||||
restrictToWorkspace: boolean
|
restrictToWorkspace: boolean
|
||||||
allowRemote: boolean
|
allowRemote: boolean
|
||||||
|
allowCommand: boolean
|
||||||
|
cronExecTimeoutMinutes: string
|
||||||
maxTokens: string
|
maxTokens: string
|
||||||
maxToolIterations: string
|
maxToolIterations: string
|
||||||
summarizeMessageThreshold: string
|
summarizeMessageThreshold: string
|
||||||
@@ -56,6 +58,8 @@ export const EMPTY_FORM: CoreConfigForm = {
|
|||||||
workspace: "",
|
workspace: "",
|
||||||
restrictToWorkspace: true,
|
restrictToWorkspace: true,
|
||||||
allowRemote: true,
|
allowRemote: true,
|
||||||
|
allowCommand: true,
|
||||||
|
cronExecTimeoutMinutes: "5",
|
||||||
maxTokens: "32768",
|
maxTokens: "32768",
|
||||||
maxToolIterations: "50",
|
maxToolIterations: "50",
|
||||||
summarizeMessageThreshold: "20",
|
summarizeMessageThreshold: "20",
|
||||||
@@ -106,6 +110,7 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
|||||||
const heartbeat = asRecord(root.heartbeat)
|
const heartbeat = asRecord(root.heartbeat)
|
||||||
const devices = asRecord(root.devices)
|
const devices = asRecord(root.devices)
|
||||||
const tools = asRecord(root.tools)
|
const tools = asRecord(root.tools)
|
||||||
|
const cron = asRecord(tools.cron)
|
||||||
const exec = asRecord(tools.exec)
|
const exec = asRecord(tools.exec)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -118,6 +123,14 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
|||||||
exec.allow_remote === undefined
|
exec.allow_remote === undefined
|
||||||
? EMPTY_FORM.allowRemote
|
? EMPTY_FORM.allowRemote
|
||||||
: asBool(exec.allow_remote),
|
: asBool(exec.allow_remote),
|
||||||
|
allowCommand:
|
||||||
|
cron.allow_command === undefined
|
||||||
|
? EMPTY_FORM.allowCommand
|
||||||
|
: asBool(cron.allow_command),
|
||||||
|
cronExecTimeoutMinutes: asNumberString(
|
||||||
|
cron.exec_timeout_minutes,
|
||||||
|
EMPTY_FORM.cronExecTimeoutMinutes,
|
||||||
|
),
|
||||||
maxTokens: asNumberString(defaults.max_tokens, EMPTY_FORM.maxTokens),
|
maxTokens: asNumberString(defaults.max_tokens, EMPTY_FORM.maxTokens),
|
||||||
maxToolIterations: asNumberString(
|
maxToolIterations: asNumberString(
|
||||||
defaults.max_tool_iterations,
|
defaults.max_tool_iterations,
|
||||||
|
|||||||
@@ -394,6 +394,10 @@
|
|||||||
"restrict_workspace_hint": "Only allow file operations inside workspace.",
|
"restrict_workspace_hint": "Only allow file operations inside workspace.",
|
||||||
"allow_remote": "Allow Remote Shell Execution",
|
"allow_remote": "Allow Remote Shell Execution",
|
||||||
"allow_remote_hint": "When enabled, shell commands can also run for remote sessions or non-local contexts. When disabled, shell execution stays limited to local safe contexts.",
|
"allow_remote_hint": "When enabled, shell commands can also run for remote sessions or non-local contexts. When disabled, shell execution stays limited to local safe contexts.",
|
||||||
|
"allow_shell_execution": "Allow Shell Execution",
|
||||||
|
"allow_shell_execution_hint": "Enable scheduled shell commands for cron jobs by default. When disabled, users must pass command_confirm=true to schedule a cron command.",
|
||||||
|
"cron_exec_timeout": "Cron Command Timeout (minutes)",
|
||||||
|
"cron_exec_timeout_hint": "Maximum runtime for scheduled shell commands. Set to 0 to disable the timeout.",
|
||||||
"max_tokens": "Max Tokens",
|
"max_tokens": "Max Tokens",
|
||||||
"max_tokens_hint": "Upper token limit per model response.",
|
"max_tokens_hint": "Upper token limit per model response.",
|
||||||
"max_tool_iterations": "Max Tool Iterations",
|
"max_tool_iterations": "Max Tool Iterations",
|
||||||
@@ -434,6 +438,7 @@
|
|||||||
"sections": {
|
"sections": {
|
||||||
"agent": "Agent",
|
"agent": "Agent",
|
||||||
"runtime": "Runtime",
|
"runtime": "Runtime",
|
||||||
|
"cron": "Cron Tasks",
|
||||||
"launcher": "Service",
|
"launcher": "Service",
|
||||||
"devices": "Devices"
|
"devices": "Devices"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -394,6 +394,10 @@
|
|||||||
"restrict_workspace_hint": "仅允许在工作目录内执行文件操作。",
|
"restrict_workspace_hint": "仅允许在工作目录内执行文件操作。",
|
||||||
"allow_remote": "允许远程执行 Shell 命令",
|
"allow_remote": "允许远程执行 Shell 命令",
|
||||||
"allow_remote_hint": "开启后,来自远程会话或非本地上下文的请求也可以执行 shell 命令;关闭后,仅允许本地安全上下文执行。",
|
"allow_remote_hint": "开启后,来自远程会话或非本地上下文的请求也可以执行 shell 命令;关闭后,仅允许本地安全上下文执行。",
|
||||||
|
"allow_shell_execution": "允许 Shell 执行",
|
||||||
|
"allow_shell_execution_hint": "开启后,cron 定时任务默认允许执行 shell 命令。关闭后,必须显式传入 command_confirm=true 才能创建 cron 命令任务。",
|
||||||
|
"cron_exec_timeout": "定时命令超时(分钟)",
|
||||||
|
"cron_exec_timeout_hint": "定时 shell 命令的最长执行时间。设置为 0 表示不限制超时。",
|
||||||
"max_tokens": "最大 Token 数",
|
"max_tokens": "最大 Token 数",
|
||||||
"max_tokens_hint": "单次模型响应允许的最大 Token 数。",
|
"max_tokens_hint": "单次模型响应允许的最大 Token 数。",
|
||||||
"max_tool_iterations": "最大工具迭代次数",
|
"max_tool_iterations": "最大工具迭代次数",
|
||||||
@@ -434,6 +438,7 @@
|
|||||||
"sections": {
|
"sections": {
|
||||||
"agent": "智能体",
|
"agent": "智能体",
|
||||||
"runtime": "运行时",
|
"runtime": "运行时",
|
||||||
|
"cron": "定时任务",
|
||||||
"launcher": "服务参数",
|
"launcher": "服务参数",
|
||||||
"devices": "设备"
|
"devices": "设备"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user