mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 10:15:12 +00:00
feat(cron): add command payload form (#1289)
This commit is contained in:
1 parent
a90127c3d6
commit
24a6b02506
6 files changed
+74
-24
No files matched your search
@@ -147,7 +147,8 @@
|
||||
"envVars": "{{count}} vars",
|
||||
"commandJson": "Command JSON",
|
||||
"commandJsonHelp": "Edit the command payload as JSON. argv is required and must be a non-empty string array.",
|
||||
"invalidCommandJson": "Invalid command JSON: argv must be a non-empty array."
|
||||
"invalidCommandJson": "Invalid command JSON: argv must be a non-empty array.",
|
||||
"commandArgvHelp": "One argument per line. Use sh / -c / your script for shell syntax."
|
||||
},
|
||||
"stateless": "Stateless",
|
||||
"statelessHelp": "Skip session persistence. Each run starts fresh without loading previous messages. Saves tokens for crons that don't need past context.",
|
||||
|
||||
@@ -147,7 +147,8 @@
|
||||
"envVars": "{{count}}개",
|
||||
"commandJson": "명령 JSON",
|
||||
"commandJsonHelp": "명령 payload를 JSON으로 수정합니다. argv는 필수이며 비어 있지 않은 문자열 배열이어야 합니다.",
|
||||
"invalidCommandJson": "명령 JSON이 올바르지 않습니다: argv는 비어 있지 않은 배열이어야 합니다."
|
||||
"invalidCommandJson": "명령 JSON이 올바르지 않습니다: argv는 비어 있지 않은 배열이어야 합니다.",
|
||||
"commandArgvHelp": "한 줄에 인자 하나씩 입력하세요. shell 문법은 sh / -c / script 형태를 사용하세요."
|
||||
},
|
||||
"stateless": "상태 없음",
|
||||
"statelessHelp": "세션 지속성 건너뜁니다. 각 실행은 이전 메시지 없이 새로 시작됩니다. 이전 컨텍스트가 필요 없는 cron의 토큰을 절약합니다.",
|
||||
|
||||
@@ -147,7 +147,8 @@
|
||||
"envVars": "{{count}} biến",
|
||||
"commandJson": "JSON lệnh",
|
||||
"commandJsonHelp": "Chỉnh sửa payload lệnh dưới dạng JSON. argv là bắt buộc và phải là mảng chuỗi không rỗng.",
|
||||
"invalidCommandJson": "JSON lệnh không hợp lệ: argv phải là mảng không rỗng."
|
||||
"invalidCommandJson": "JSON lệnh không hợp lệ: argv phải là mảng không rỗng.",
|
||||
"commandArgvHelp": "Mỗi dòng là một đối số. Dùng sh / -c / script nếu cần cú pháp shell."
|
||||
},
|
||||
"stateless": "Không trạng thái",
|
||||
"statelessHelp": "Bỏ qua lưu session. Mỗi lần chạy bắt đầu mới. Tiết kiệm token cho các cron không cần context cũ.",
|
||||
|
||||
@@ -147,7 +147,8 @@
|
||||
"envVars": "{{count}}个变量",
|
||||
"commandJson": "命令 JSON",
|
||||
"commandJsonHelp": "以 JSON 编辑命令 payload。argv 必填,且必须是非空字符串数组。",
|
||||
"invalidCommandJson": "命令 JSON 无效:argv 必须是非空数组。"
|
||||
"invalidCommandJson": "命令 JSON 无效:argv 必须是非空数组。",
|
||||
"commandArgvHelp": "每行一个参数。如需 shell 语法,请使用 sh / -c / script。"
|
||||
},
|
||||
"stateless": "无状态",
|
||||
"statelessHelp": "跳过会话持久化。每次运行从零开始。为不需要历史上下文的定时任务节省令牌。",
|
||||
|
||||
@@ -41,7 +41,12 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
|
||||
name: "",
|
||||
payloadKind: "agent_turn",
|
||||
message: "",
|
||||
commandJson: JSON.stringify({ argv: ["sh", "-c", "echo hello"] }, null, 2),
|
||||
commandArgvText: "sh\n-c\necho hello",
|
||||
commandCwd: "",
|
||||
commandTimeoutSeconds: "",
|
||||
commandNoOutputTimeoutSeconds: "",
|
||||
commandOutputMaxBytes: "",
|
||||
commandInput: "",
|
||||
agentId: "",
|
||||
scheduleKind: "every",
|
||||
everyValue: "60",
|
||||
@@ -63,7 +68,14 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
|
||||
}
|
||||
|
||||
const command = data.payloadKind === "command"
|
||||
? JSON.parse(data.commandJson || "{}") as CronCommandSpec
|
||||
? {
|
||||
argv: (data.commandArgvText || "").split("\n").map((v) => v.trim()).filter(Boolean),
|
||||
cwd: data.commandCwd?.trim() || undefined,
|
||||
timeoutSeconds: data.commandTimeoutSeconds ? Number(data.commandTimeoutSeconds) : undefined,
|
||||
noOutputTimeoutSeconds: data.commandNoOutputTimeoutSeconds ? Number(data.commandNoOutputTimeoutSeconds) : undefined,
|
||||
outputMaxBytes: data.commandOutputMaxBytes ? Number(data.commandOutputMaxBytes) : undefined,
|
||||
input: data.commandInput || undefined,
|
||||
} satisfies CronCommandSpec
|
||||
: undefined;
|
||||
|
||||
await onSubmit({
|
||||
@@ -200,18 +212,41 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandJson")}</Label>
|
||||
<Textarea
|
||||
{...register("commandJson")}
|
||||
rows={8}
|
||||
className="font-mono text-base md:text-sm"
|
||||
/>
|
||||
{errors.commandJson ? (
|
||||
<p className="text-xs text-destructive">{errors.commandJson.message}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">{t("detail.commandJsonHelp")}</p>
|
||||
)}
|
||||
<div className="space-y-3 rounded-md border p-3">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandArgv")}</Label>
|
||||
<Textarea {...register("commandArgvText")} rows={4} className="font-mono text-base md:text-sm" />
|
||||
{errors.commandArgvText ? (
|
||||
<p className="text-xs text-destructive">{errors.commandArgvText.message}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">{t("detail.commandArgvHelp")}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandCwd")}</Label>
|
||||
<Input {...register("commandCwd")} placeholder={t("detail.defaultWorkingDirectory")} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandTimeout")}</Label>
|
||||
<Input type="number" min={0} {...register("commandTimeoutSeconds")} placeholder={t("detail.defaultValue")} />
|
||||
{errors.commandTimeoutSeconds && <p className="text-xs text-destructive">{errors.commandTimeoutSeconds.message}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandNoOutputTimeout")}</Label>
|
||||
<Input type="number" min={0} {...register("commandNoOutputTimeoutSeconds")} placeholder={t("detail.none")} />
|
||||
{errors.commandNoOutputTimeoutSeconds && <p className="text-xs text-destructive">{errors.commandNoOutputTimeoutSeconds.message}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandOutputLimit")}</Label>
|
||||
<Input type="number" min={0} {...register("commandOutputMaxBytes")} placeholder={t("detail.defaultValue")} />
|
||||
{errors.commandOutputMaxBytes && <p className="text-xs text-destructive">{errors.commandOutputMaxBytes.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("detail.commandInput")}</Label>
|
||||
<Textarea {...register("commandInput")} rows={3} className="font-mono text-base md:text-sm" placeholder={t("detail.none")} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -221,7 +256,7 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit(onFormSubmit)}
|
||||
disabled={isSubmitting || !!errors.name || (payloadKind === "agent_turn" ? !!errors.message : !!errors.commandJson)}
|
||||
disabled={isSubmitting || !!errors.name || (payloadKind === "agent_turn" ? !!errors.message : !!errors.commandArgvText)}
|
||||
>
|
||||
{isSubmitting ? t("create.creating") : t("create.create")}
|
||||
</Button>
|
||||
|
||||
@@ -8,7 +8,12 @@ export const cronCreateSchema = z.object({
|
||||
.refine(isValidSlug, "Only lowercase letters, numbers, and hyphens"),
|
||||
payloadKind: z.enum(["agent_turn", "command"]),
|
||||
message: z.string().optional(),
|
||||
commandJson: z.string().optional(),
|
||||
commandArgvText: z.string().optional(),
|
||||
commandCwd: z.string().optional(),
|
||||
commandTimeoutSeconds: z.string().optional(),
|
||||
commandNoOutputTimeoutSeconds: z.string().optional(),
|
||||
commandOutputMaxBytes: z.string().optional(),
|
||||
commandInput: z.string().optional(),
|
||||
agentId: z.string().optional(),
|
||||
scheduleKind: z.enum(["every", "cron", "at"]),
|
||||
everyValue: z.string().min(1, "Required"),
|
||||
@@ -19,12 +24,18 @@ export const cronCreateSchema = z.object({
|
||||
}
|
||||
if (data.payloadKind === "command") {
|
||||
try {
|
||||
const parsed = JSON.parse(data.commandJson || "");
|
||||
if (!Array.isArray(parsed?.argv) || parsed.argv.length === 0 || !parsed.argv[0]) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandJson"], message: "argv must be a non-empty array" });
|
||||
const argv = (data.commandArgvText || "").split("\n").map((v) => v.trim()).filter(Boolean);
|
||||
if (argv.length === 0 || !argv[0]) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandArgvText"], message: "argv must be a non-empty list" });
|
||||
}
|
||||
for (const field of ["commandTimeoutSeconds", "commandNoOutputTimeoutSeconds", "commandOutputMaxBytes"] as const) {
|
||||
const raw = data[field];
|
||||
if (raw && (!/^\d+$/.test(raw) || Number(raw) < 0)) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [field], message: "Must be a non-negative integer" });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandJson"], message: "Invalid JSON" });
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandArgvText"], message: "Invalid command" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user