diff --git a/ui/web/src/i18n/locales/en/cron.json b/ui/web/src/i18n/locales/en/cron.json
index e169a897..62f30a73 100644
--- a/ui/web/src/i18n/locales/en/cron.json
+++ b/ui/web/src/i18n/locales/en/cron.json
@@ -143,7 +143,10 @@
"bytes": "{{count}} bytes",
"defaultValue": "Default",
"none": "None",
- "envVars": "{{count}} vars"
+ "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."
},
"stateless": "Stateless",
"statelessHelp": "Skip session persistence. Each run starts fresh without loading previous messages. Saves tokens for crons that don't need past context.",
diff --git a/ui/web/src/i18n/locales/ko/cron.json b/ui/web/src/i18n/locales/ko/cron.json
index 8ebad7ce..3fe81362 100644
--- a/ui/web/src/i18n/locales/ko/cron.json
+++ b/ui/web/src/i18n/locales/ko/cron.json
@@ -143,7 +143,10 @@
"bytes": "{{count}} bytes",
"defaultValue": "기본값",
"none": "없음",
- "envVars": "{{count}}개"
+ "envVars": "{{count}}개",
+ "commandJson": "명령 JSON",
+ "commandJsonHelp": "명령 payload를 JSON으로 수정합니다. argv는 필수이며 비어 있지 않은 문자열 배열이어야 합니다.",
+ "invalidCommandJson": "명령 JSON이 올바르지 않습니다: argv는 비어 있지 않은 배열이어야 합니다."
},
"stateless": "상태 없음",
"statelessHelp": "세션 지속성 건너뜁니다. 각 실행은 이전 메시지 없이 새로 시작됩니다. 이전 컨텍스트가 필요 없는 cron의 토큰을 절약합니다.",
diff --git a/ui/web/src/i18n/locales/vi/cron.json b/ui/web/src/i18n/locales/vi/cron.json
index 480e3cf4..44aadbac 100644
--- a/ui/web/src/i18n/locales/vi/cron.json
+++ b/ui/web/src/i18n/locales/vi/cron.json
@@ -143,7 +143,10 @@
"bytes": "{{count}} bytes",
"defaultValue": "Mặc định",
"none": "Không có",
- "envVars": "{{count}} biến"
+ "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."
},
"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ũ.",
diff --git a/ui/web/src/i18n/locales/zh/cron.json b/ui/web/src/i18n/locales/zh/cron.json
index b1263a84..f00d3fb0 100644
--- a/ui/web/src/i18n/locales/zh/cron.json
+++ b/ui/web/src/i18n/locales/zh/cron.json
@@ -143,7 +143,10 @@
"bytes": "{{count}} bytes",
"defaultValue": "默认值",
"none": "无",
- "envVars": "{{count}}个变量"
+ "envVars": "{{count}}个变量",
+ "commandJson": "命令 JSON",
+ "commandJsonHelp": "以 JSON 编辑命令 payload。argv 必填,且必须是非空字符串数组。",
+ "invalidCommandJson": "命令 JSON 无效:argv 必须是非空数组。"
},
"stateless": "无状态",
"statelessHelp": "跳过会话持久化。每次运行从零开始。为不需要历史上下文的定时任务节省令牌。",
diff --git a/ui/web/src/pages/cron/cron-detail/cron-overview-tab.tsx b/ui/web/src/pages/cron/cron-detail/cron-overview-tab.tsx
index 8e40b6b5..4c4eb6bb 100644
--- a/ui/web/src/pages/cron/cron-detail/cron-overview-tab.tsx
+++ b/ui/web/src/pages/cron/cron-detail/cron-overview-tab.tsx
@@ -53,6 +53,7 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
const [agentId, setAgentId] = useState(job.agentId ?? "");
const [enabled, setEnabled] = useState(job.enabled);
const [editingMessage, setEditingMessage] = useState(false);
+ const [editingCommand, setEditingCommand] = useState(false);
// Delivery fields
const [deliver, setDeliver] = useState(job.deliver ?? false);
@@ -67,6 +68,7 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
const [saving, setSaving] = useState(false);
const command = job.payload?.command;
+ const [commandJson, setCommandJson] = useState(() => JSON.stringify(command ?? { argv: [] }, null, 2));
const commandEnvCount = command?.env ? Object.keys(command.env).length : 0;
// Fetch delivery targets on mount
@@ -88,6 +90,23 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
toast.error(t("detail.invalidTimezone", "Invalid timezone"));
return;
}
+
+ const commandChanged = isCommandCron(job)
+ && commandJson.trim() !== JSON.stringify(command ?? { argv: [] }, null, 2).trim();
+ let parsedCommand: import("../hooks/use-cron").CronCommandSpec | undefined;
+ if (commandChanged) {
+ try {
+ parsedCommand = JSON.parse(commandJson);
+ if (!Array.isArray(parsedCommand?.argv) || parsedCommand.argv.length === 0 || !parsedCommand.argv[0]) {
+ toast.error(t("detail.invalidCommandJson"));
+ return;
+ }
+ } catch {
+ toast.error(t("detail.invalidCommandJson"));
+ return;
+ }
+ }
+
setSaving(true);
try {
let schedule;
@@ -101,6 +120,7 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
const patch: import("../hooks/use-cron").CronJobPatch = {
schedule,
message: isCommandCron(job) ? undefined : message.trim(),
+ command: commandChanged ? parsedCommand : undefined,
agentId: agentId.trim() || "",
deliver,
deliverChannel: deliver ? channel.trim() || undefined : undefined,
@@ -113,6 +133,7 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
if (enabled !== job.enabled) patch.enabled = enabled;
await onUpdate(job.id, patch);
setEditingMessage(false);
+ setEditingCommand(false);
} catch {
// toast shown by hook
} finally {
@@ -143,19 +164,37 @@ export function CronOverviewTab({ job, onUpdate }: CronOverviewTabProps) {
{formatCommand(job)}
- ) : (
- {t("detail.noCommand")}
+{formatCommand(job)}
+ ) : (
+ {t("detail.noCommand")}
+ )} +