mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fcb69860c4
- 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
194 lines
5.3 KiB
TypeScript
194 lines
5.3 KiB
TypeScript
export type JsonRecord = Record<string, unknown>
|
|
|
|
export interface CoreConfigForm {
|
|
workspace: string
|
|
restrictToWorkspace: boolean
|
|
allowRemote: boolean
|
|
allowCommand: boolean
|
|
cronExecTimeoutMinutes: string
|
|
maxTokens: string
|
|
maxToolIterations: string
|
|
summarizeMessageThreshold: string
|
|
summarizeTokenPercent: string
|
|
dmScope: string
|
|
heartbeatEnabled: boolean
|
|
heartbeatInterval: string
|
|
devicesEnabled: boolean
|
|
monitorUSB: boolean
|
|
}
|
|
|
|
export interface LauncherForm {
|
|
port: string
|
|
publicAccess: boolean
|
|
allowedCIDRsText: string
|
|
}
|
|
|
|
export const DM_SCOPE_OPTIONS = [
|
|
{
|
|
value: "per-channel-peer",
|
|
labelKey: "pages.config.session_scope_per_channel_peer",
|
|
labelDefault: "Per Channel + Peer",
|
|
descKey: "pages.config.session_scope_per_channel_peer_desc",
|
|
descDefault: "Separate context for each user in each channel.",
|
|
},
|
|
{
|
|
value: "per-channel",
|
|
labelKey: "pages.config.session_scope_per_channel",
|
|
labelDefault: "Per Channel",
|
|
descKey: "pages.config.session_scope_per_channel_desc",
|
|
descDefault: "One shared context per channel.",
|
|
},
|
|
{
|
|
value: "per-peer",
|
|
labelKey: "pages.config.session_scope_per_peer",
|
|
labelDefault: "Per Peer",
|
|
descKey: "pages.config.session_scope_per_peer_desc",
|
|
descDefault: "One context per user across channels.",
|
|
},
|
|
{
|
|
value: "global",
|
|
labelKey: "pages.config.session_scope_global",
|
|
labelDefault: "Global",
|
|
descKey: "pages.config.session_scope_global_desc",
|
|
descDefault: "All messages share one global context.",
|
|
},
|
|
] as const
|
|
|
|
export const EMPTY_FORM: CoreConfigForm = {
|
|
workspace: "",
|
|
restrictToWorkspace: true,
|
|
allowRemote: true,
|
|
allowCommand: true,
|
|
cronExecTimeoutMinutes: "5",
|
|
maxTokens: "32768",
|
|
maxToolIterations: "50",
|
|
summarizeMessageThreshold: "20",
|
|
summarizeTokenPercent: "75",
|
|
dmScope: "per-channel-peer",
|
|
heartbeatEnabled: true,
|
|
heartbeatInterval: "30",
|
|
devicesEnabled: false,
|
|
monitorUSB: true,
|
|
}
|
|
|
|
export const EMPTY_LAUNCHER_FORM: LauncherForm = {
|
|
port: "18800",
|
|
publicAccess: false,
|
|
allowedCIDRsText: "",
|
|
}
|
|
|
|
function asRecord(value: unknown): JsonRecord {
|
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
return value as JsonRecord
|
|
}
|
|
return {}
|
|
}
|
|
|
|
function asString(value: unknown): string {
|
|
return typeof value === "string" ? value : ""
|
|
}
|
|
|
|
function asBool(value: unknown): boolean {
|
|
return value === true
|
|
}
|
|
|
|
function asNumberString(value: unknown, fallback: string): string {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return String(value)
|
|
}
|
|
if (typeof value === "string" && value.trim() !== "") {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
|
const root = asRecord(config)
|
|
const agents = asRecord(root.agents)
|
|
const defaults = asRecord(agents.defaults)
|
|
const session = asRecord(root.session)
|
|
const heartbeat = asRecord(root.heartbeat)
|
|
const devices = asRecord(root.devices)
|
|
const tools = asRecord(root.tools)
|
|
const cron = asRecord(tools.cron)
|
|
const exec = asRecord(tools.exec)
|
|
|
|
return {
|
|
workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
|
|
restrictToWorkspace:
|
|
defaults.restrict_to_workspace === undefined
|
|
? EMPTY_FORM.restrictToWorkspace
|
|
: asBool(defaults.restrict_to_workspace),
|
|
allowRemote:
|
|
exec.allow_remote === undefined
|
|
? EMPTY_FORM.allowRemote
|
|
: 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),
|
|
maxToolIterations: asNumberString(
|
|
defaults.max_tool_iterations,
|
|
EMPTY_FORM.maxToolIterations,
|
|
),
|
|
summarizeMessageThreshold: asNumberString(
|
|
defaults.summarize_message_threshold,
|
|
EMPTY_FORM.summarizeMessageThreshold,
|
|
),
|
|
summarizeTokenPercent: asNumberString(
|
|
defaults.summarize_token_percent,
|
|
EMPTY_FORM.summarizeTokenPercent,
|
|
),
|
|
dmScope: asString(session.dm_scope) || EMPTY_FORM.dmScope,
|
|
heartbeatEnabled:
|
|
heartbeat.enabled === undefined
|
|
? EMPTY_FORM.heartbeatEnabled
|
|
: asBool(heartbeat.enabled),
|
|
heartbeatInterval: asNumberString(
|
|
heartbeat.interval,
|
|
EMPTY_FORM.heartbeatInterval,
|
|
),
|
|
devicesEnabled:
|
|
devices.enabled === undefined
|
|
? EMPTY_FORM.devicesEnabled
|
|
: asBool(devices.enabled),
|
|
monitorUSB:
|
|
devices.monitor_usb === undefined
|
|
? EMPTY_FORM.monitorUSB
|
|
: asBool(devices.monitor_usb),
|
|
}
|
|
}
|
|
|
|
export function parseIntField(
|
|
rawValue: string,
|
|
label: string,
|
|
options: { min?: number; max?: number } = {},
|
|
): number {
|
|
const value = Number(rawValue)
|
|
if (!Number.isInteger(value)) {
|
|
throw new Error(`${label} must be an integer.`)
|
|
}
|
|
if (options.min !== undefined && value < options.min) {
|
|
throw new Error(`${label} must be >= ${options.min}.`)
|
|
}
|
|
if (options.max !== undefined && value > options.max) {
|
|
throw new Error(`${label} must be <= ${options.max}.`)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function parseCIDRText(raw: string): string[] {
|
|
if (!raw.trim()) {
|
|
return []
|
|
}
|
|
return raw
|
|
.split(/[\n,]/)
|
|
.map((v) => v.trim())
|
|
.filter((v) => v.length > 0)
|
|
}
|