From 3e6ed4fd43c7e14df7edb2095e0b50b57bee5b5e Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:03:10 +0800 Subject: [PATCH 01/46] =?UTF-8?q?=E2=9C=A8=20feat(agents):=20add=20cherry-?= =?UTF-8?q?claw=20agent=20type=20to=20type=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'cherry-claw' to AgentTypeSchema enum - Define CherryClawConfiguration, SchedulerType, CherryClawChannel types - Add DEFAULT_CHERRY_CLAW_CONFIG with bypassPermissions default - Add cherry-claw avatar placeholder - Update OpenAPI spec and Swagger docs with new agent type Signed-off-by: Vaayne --- .../apiServer/generated/openapi-spec.json | 2 +- src/main/apiServer/routes/agents/index.ts | 2 +- .../src/assets/images/models/cherry-claw.png | Bin 0 -> 14350 bytes src/renderer/src/config/agent.ts | 18 ++++++++- src/renderer/src/types/agent.ts | 38 +++++++++++++++++- 5 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/renderer/src/assets/images/models/cherry-claw.png diff --git a/src/main/apiServer/generated/openapi-spec.json b/src/main/apiServer/generated/openapi-spec.json index 353e156511c..3d198a76101 100644 --- a/src/main/apiServer/generated/openapi-spec.json +++ b/src/main/apiServer/generated/openapi-spec.json @@ -216,7 +216,7 @@ }, "AgentType": { "type": "string", - "enum": ["claude-code"], + "enum": ["claude-code", "cherry-claw"], "description": "Type of agent" }, "AgentConfiguration": { diff --git a/src/main/apiServer/routes/agents/index.ts b/src/main/apiServer/routes/agents/index.ts index 42843b72018..41987822b22 100644 --- a/src/main/apiServer/routes/agents/index.ts +++ b/src/main/apiServer/routes/agents/index.ts @@ -30,7 +30,7 @@ const agentsRouter = express.Router() * * AgentType: * type: string - * enum: [claude-code] + * enum: [claude-code, cherry-claw] * description: Type of agent * * AgentConfiguration: diff --git a/src/renderer/src/assets/images/models/cherry-claw.png b/src/renderer/src/assets/images/models/cherry-claw.png new file mode 100644 index 0000000000000000000000000000000000000000..560598ed1ffe3df6f6e4bc37f880f1065c00c614 GIT binary patch literal 14350 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/config/agent.ts b/src/renderer/src/config/agent.ts index aae52ce3f27..02e7e4d5052 100644 --- a/src/renderer/src/config/agent.ts +++ b/src/renderer/src/config/agent.ts @@ -1,5 +1,6 @@ +import CherryClawAvatar from '@renderer/assets/images/models/cherry-claw.png' import ClaudeAvatar from '@renderer/assets/images/models/claude.png' -import type { AgentBase, AgentType } from '@renderer/types' +import type { AgentBase, AgentType, CherryClawConfiguration } from '@renderer/types' import type { PermissionModeCard } from '@renderer/types/agent' // base agent config. no default config for now. @@ -12,10 +13,25 @@ export const DEFAULT_CLAUDE_CODE_CONFIG: Omit = { ...DEFAULT_AGENT_CONFIG } as const +export const DEFAULT_CHERRY_CLAW_CONFIG: Omit & { configuration: CherryClawConfiguration } = { + ...DEFAULT_AGENT_CONFIG, + configuration: { + permission_mode: 'bypassPermissions', + max_turns: 100, + soul_enabled: true, + scheduler_enabled: false, + scheduler_type: 'interval', + heartbeat_enabled: true, + heartbeat_file: 'heartbeat.md' + } +} as const + export const getAgentTypeAvatar = (type: AgentType): string => { switch (type) { case 'claude-code': return ClaudeAvatar + case 'cherry-claw': + return CherryClawAvatar default: return '' } diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index 1931b8bbe72..b98e20d4bbd 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -25,9 +25,23 @@ export const SessionMessageRoleSchema = z.enum(sessionMessageRoles) export type SessionMessageType = TextStreamPart>['type'] -export const AgentTypeSchema = z.enum(['claude-code']) +export const AgentTypeSchema = z.enum(['claude-code', 'cherry-claw']) export type AgentType = z.infer +// ------------------ CherryClaw-specific types ------------------ +export const SchedulerTypeSchema = z.enum(['cron', 'interval', 'one-time']) +export type SchedulerType = z.infer + +export const CherryClawChannelSchema = z.object({ + id: z.string(), + type: z.string(), + name: z.string(), + config: z.record(z.string(), z.unknown()), + is_notify_receiver: z.boolean() +}) + +export type CherryClawChannel = z.infer + export const isAgentType = (type: unknown): type is AgentType => { return AgentTypeSchema.safeParse(type).success } @@ -64,6 +78,28 @@ export const AgentConfigurationSchema = z export type AgentConfiguration = z.infer +// CherryClaw extends AgentConfiguration with scheduler/soul/heartbeat fields. +// Since AgentConfigurationSchema uses .loose(), these are stored in the same JSON field. +export type CherryClawConfiguration = AgentConfiguration & { + // Soul + soul_enabled?: boolean + + // Scheduler + scheduler_enabled?: boolean + scheduler_type?: SchedulerType + scheduler_cron?: string + scheduler_interval?: number + scheduler_one_time_delay?: number + scheduler_last_run?: string + + // Heartbeat + heartbeat_enabled?: boolean + heartbeat_file?: string + + // Channels (placeholder) + channels?: CherryClawChannel[] +} + // Shared configuration interface for both agents and sessions export const AgentBaseSchema = z.object({ // Basic info From 5e6c0b5bab420b00bc89f01f1b205e88717f3960 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:04:18 +0800 Subject: [PATCH 02/46] =?UTF-8?q?=F0=9F=8C=90=20feat(i18n):=20add=20Cherry?= =?UTF-8?q?Claw=20agent=20i18n=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add all CherryClaw-related UI strings for soul, scheduler, heartbeat, channels (placeholder), agent type names, and bypass warning. Signed-off-by: Vaayne --- src/renderer/src/i18n/locales/en-us.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/locales/zh-cn.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/locales/zh-tw.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/de-de.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/el-gr.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/es-es.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/fr-fr.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/ja-jp.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/pt-pt.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/ro-ro.json | 73 ++++++++++++++++++++++ src/renderer/src/i18n/translate/ru-ru.json | 73 ++++++++++++++++++++++ 11 files changed, 803 insertions(+) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 3c8bb80a947..d9ed86ca43b 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -26,6 +26,79 @@ "submit": "Submit", "title": "Questions from Agent" }, + "cherryClaw": { + "channels": { + "comingSoon": "Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "Channels", + "title": "Channels" + }, + "heartbeat": { + "description": "The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "No heartbeat file found. Create one in the workspace root.", + "enabled": "Enable Heartbeat", + "enabledHelper": "When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "Heartbeat File", + "placeholder": "heartbeat.md" + }, + "preview": "Heartbeat Preview", + "tab": "Heartbeat", + "title": "Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "Cron Expression", + "placeholder": "*/30 * * * *" + }, + "description": "The scheduler triggers the agent autonomously on a schedule.", + "enabled": "Enable Scheduler", + "enabledHelper": "When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "Invalid cron expression", + "invalidDelay": "Delay must be a positive number", + "invalidInterval": "Interval must be a positive number" + }, + "interval": { + "helper": "How often the agent should run, in seconds.", + "label": "Interval (seconds)" + }, + "lastRun": "Last Run", + "never": "Never", + "nextRun": "Next Run", + "oneTime": { + "helper": "One-time delay in seconds before the agent runs.", + "label": "Delay (seconds)" + }, + "status": { + "running": "Running", + "stopped": "Stopped", + "tickInProgress": "Tick in progress" + }, + "tab": "Scheduler", + "title": "Scheduler Configuration", + "type": { + "cron": "Cron Expression", + "interval": "Fixed Interval", + "label": "Schedule Type", + "one-time": "One-Time Delay" + } + }, + "soul": { + "description": "The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "Enable Soul", + "enabledHelper": "When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "soul.md", + "preview": "Soul Preview", + "tab": "Soul", + "title": "Soul Configuration" + }, + "warning": { + "bypassPermissions": "CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Deleting the agent will forcibly stop and delete all sessions associated with the agent. Are you sure?", "error": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 36d90127dd1..8998adb1863 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -26,6 +26,79 @@ "submit": "提交", "title": "来自 Agent 的问题" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "删除该 Agent 将强制终止并删除该 Agent 下的所有会话。您确定吗?", "error": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index daf44c07540..0b6308a4088 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -26,6 +26,79 @@ "submit": "提交", "title": "來自Agent的提問" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "刪除該 Agent 會強制停止並刪除該 Agent 的所有工作階段。確定要刪除嗎?", "error": { diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 705fe271d1a..703fe0c592d 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -26,6 +26,79 @@ "submit": "Einreichen", "title": "Fragen vom Agenten" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Das Löschen dieses Agents wird alle Sitzungen unter diesem Agent zwangsweise beenden und löschen. Sind Sie sicher?", "error": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index befdd6ba4b3..a3452b5db64 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -26,6 +26,79 @@ "submit": "Υποβολή", "title": "Ερωτήσεις από τον πράκτορα" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Η διαγραφή αυτού του Agent θα τερματίσει βίαια και θα διαγράψει όλες τις συνεδρίες υπό αυτόν τον Agent. Είστε σίγουροι;", "error": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index a90f760b8e8..834718c4790 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -26,6 +26,79 @@ "submit": "Enviar", "title": "Preguntas del Agente" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Eliminar este Agente forzará la terminación y eliminación de todas las sesiones bajo este Agente. ¿Está seguro?", "error": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index b3c3bd6ba74..6f93d95625b 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -26,6 +26,79 @@ "submit": "Soumettre", "title": "Questions de l'agent" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "La suppression de cet Agent entraînera la terminaison forcée et la suppression de toutes les sessions associées. Êtes-vous certain ?", "error": { diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 85b8de18821..c0dd1d78d16 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -26,6 +26,79 @@ "submit": "送信", "title": "エージェントからの質問" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "このエージェントを削除すると、このエージェントのすべてのセッションが強制的に終了し、削除されます。本当によろしいですか?", "error": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 3e2e0dd46c0..74cecff2336 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -26,6 +26,79 @@ "submit": "Enviar", "title": "Perguntas do Agente" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Excluir este Agente forçará a terminação e exclusão de todas as sessões sob ele. Tem certeza?", "error": { diff --git a/src/renderer/src/i18n/translate/ro-ro.json b/src/renderer/src/i18n/translate/ro-ro.json index 7eb3065811d..520ac037edb 100644 --- a/src/renderer/src/i18n/translate/ro-ro.json +++ b/src/renderer/src/i18n/translate/ro-ro.json @@ -26,6 +26,79 @@ "submit": "Trimite", "title": "Întrebări de la Agent" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Ștergerea agentului va opri forțat și va șterge toate sesiunile asociate cu agentul. Ești sigur?", "error": { diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index c5e2c98a3f6..5ef193e01fe 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -26,6 +26,79 @@ "submit": "Отправить", "title": "Вопросы от агента" }, + "cherryClaw": { + "channels": { + "comingSoon": "[to be translated]:Channels are coming soon. They will allow the agent to communicate via Slack, Discord, webhooks, and more.", + "tab": "[to be translated]:Channels", + "title": "[to be translated]:Channels" + }, + "heartbeat": { + "description": "[to be translated]:The heartbeat message is sent to the agent's most recent session on each scheduler tick.", + "empty": "[to be translated]:No heartbeat file found. Create one in the workspace root.", + "enabled": "[to be translated]:Enable Heartbeat", + "enabledHelper": "[to be translated]:When enabled, the content of the heartbeat file is sent as a user message on each scheduler tick.", + "file": { + "helper": "[to be translated]:Path to the heartbeat file relative to the workspace root. Must be within accessible paths.", + "label": "[to be translated]:Heartbeat File", + "placeholder": "[to be translated]:heartbeat.md" + }, + "preview": "[to be translated]:Heartbeat Preview", + "tab": "[to be translated]:Heartbeat", + "title": "[to be translated]:Heartbeat Configuration" + }, + "scheduler": { + "cron": { + "helper": "[to be translated]:Standard cron expression (e.g., '*/30 * * * *' for every 30 minutes).", + "label": "[to be translated]:Cron Expression", + "placeholder": "[to be translated]:*/30 * * * *" + }, + "description": "[to be translated]:The scheduler triggers the agent autonomously on a schedule.", + "enabled": "[to be translated]:Enable Scheduler", + "enabledHelper": "[to be translated]:When enabled, the agent will run automatically based on the configured schedule.", + "errors": { + "invalidCron": "[to be translated]:Invalid cron expression", + "invalidDelay": "[to be translated]:Delay must be a positive number", + "invalidInterval": "[to be translated]:Interval must be a positive number" + }, + "interval": { + "helper": "[to be translated]:How often the agent should run, in seconds.", + "label": "[to be translated]:Interval (seconds)" + }, + "lastRun": "[to be translated]:Last Run", + "never": "[to be translated]:Never", + "nextRun": "[to be translated]:Next Run", + "oneTime": { + "helper": "[to be translated]:One-time delay in seconds before the agent runs.", + "label": "[to be translated]:Delay (seconds)" + }, + "status": { + "running": "[to be translated]:Running", + "stopped": "[to be translated]:Stopped", + "tickInProgress": "[to be translated]:Tick in progress" + }, + "tab": "[to be translated]:Scheduler", + "title": "[to be translated]:Scheduler Configuration", + "type": { + "cron": "[to be translated]:Cron Expression", + "interval": "[to be translated]:Fixed Interval", + "label": "[to be translated]:Schedule Type", + "one-time": "[to be translated]:One-Time Delay" + } + }, + "soul": { + "description": "[to be translated]:The soul defines the agent's personality and base behavior. It is read from soul.md in the workspace root.", + "empty": "[to be translated]:No soul.md found in the workspace root. Create one to define the agent's personality.", + "enabled": "[to be translated]:Enable Soul", + "enabledHelper": "[to be translated]:When enabled, the agent reads soul.md from the workspace root and prepends it to the system prompt.", + "fileLabel": "[to be translated]:soul.md", + "preview": "[to be translated]:Soul Preview", + "tab": "[to be translated]:Soul", + "title": "[to be translated]:Soul Configuration" + }, + "warning": { + "bypassPermissions": "[to be translated]:CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval." + } + }, "delete": { "content": "Удаление этого агента приведёт к принудительному завершению и удалению всех сессий, связанных с ним. Вы уверены?", "error": { From a175b9f42fc0e5e06100680d424c3ae00a7287e2 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:05:36 +0800 Subject: [PATCH 03/46] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(agents):=20?= =?UTF-8?q?introduce=20AgentServiceRegistry=20for=20multi-type=20dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create AgentServiceRegistry mapping AgentType → AgentServiceInterface - Refactor SessionMessageService to use registry instead of hardcoded ClaudeCodeService - Register ClaudeCodeService for 'claude-code' in services module init Signed-off-by: Vaayne --- .../agents/services/AgentServiceRegistry.ts | 41 +++++++++++++++++++ .../agents/services/SessionMessageService.ts | 11 +++-- src/main/services/agents/services/index.ts | 9 ++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/main/services/agents/services/AgentServiceRegistry.ts diff --git a/src/main/services/agents/services/AgentServiceRegistry.ts b/src/main/services/agents/services/AgentServiceRegistry.ts new file mode 100644 index 00000000000..4dad3640c59 --- /dev/null +++ b/src/main/services/agents/services/AgentServiceRegistry.ts @@ -0,0 +1,41 @@ +import { loggerService } from '@logger' +import type { AgentType } from '@types' + +import type { AgentServiceInterface } from '../interfaces/AgentStreamInterface' + +const logger = loggerService.withContext('AgentServiceRegistry') + +/** + * Registry mapping AgentType to the service that handles invocations for that type. + * Used by SessionMessageService to dispatch to the correct agent service. + */ +class AgentServiceRegistry { + private static instance: AgentServiceRegistry | null = null + private readonly services = new Map() + + static getInstance(): AgentServiceRegistry { + if (!AgentServiceRegistry.instance) { + AgentServiceRegistry.instance = new AgentServiceRegistry() + } + return AgentServiceRegistry.instance + } + + register(agentType: AgentType, service: AgentServiceInterface): void { + logger.info('Registering agent service', { agentType }) + this.services.set(agentType, service) + } + + getService(agentType: AgentType): AgentServiceInterface { + const service = this.services.get(agentType) + if (!service) { + throw new Error(`No agent service registered for type: ${agentType}`) + } + return service + } + + hasService(agentType: AgentType): boolean { + return this.services.has(agentType) + } +} + +export const agentServiceRegistry = AgentServiceRegistry.getInstance() diff --git a/src/main/services/agents/services/SessionMessageService.ts b/src/main/services/agents/services/SessionMessageService.ts index e7d5a7bfe58..64b3aafd83c 100644 --- a/src/main/services/agents/services/SessionMessageService.ts +++ b/src/main/services/agents/services/SessionMessageService.ts @@ -11,7 +11,7 @@ import { and, desc, eq, not } from 'drizzle-orm' import { BaseService } from '../BaseService' import { sessionMessagesTable } from '../database/schema' import type { AgentStreamEvent } from '../interfaces/AgentStreamInterface' -import ClaudeCodeService from './claudecode' +import { agentServiceRegistry } from './AgentServiceRegistry' const logger = loggerService.withContext('SessionMessageService') @@ -95,7 +95,6 @@ class TextStreamAccumulator { export class SessionMessageService extends BaseService { private static instance: SessionMessageService | null = null - private cc: ClaudeCodeService = new ClaudeCodeService() static getInstance(): SessionMessageService { if (!SessionMessageService.instance) { @@ -164,13 +163,13 @@ export class SessionMessageService extends BaseService { const agentSessionId = await this.getLastAgentSessionId(session.id) logger.debug('Session Message stream message data:', { message: req, session_id: agentSessionId }) - if (session.agent_type !== 'claude-code') { - // TODO: Implement support for other agent types + if (!agentServiceRegistry.hasService(session.agent_type)) { logger.error('Unsupported agent type for streaming:', { agent_type: session.agent_type }) - throw new Error('Unsupported agent type for streaming') + throw new Error(`Unsupported agent type for streaming: ${session.agent_type}`) } - const claudeStream = await this.cc.invoke(req.content, session, abortController, agentSessionId, { + const service = agentServiceRegistry.getService(session.agent_type) + const claudeStream = await service.invoke(req.content, session, abortController, agentSessionId, { effort: req.effort, thinking: req.thinking }) diff --git a/src/main/services/agents/services/index.ts b/src/main/services/agents/services/index.ts index e6e545a442a..483e3e835e0 100644 --- a/src/main/services/agents/services/index.ts +++ b/src/main/services/agents/services/index.ts @@ -15,6 +15,15 @@ export { agentService } from './AgentService' export { sessionMessageService } from './SessionMessageService' export { sessionService } from './SessionService' +// Agent service registry +export { agentServiceRegistry } from './AgentServiceRegistry' + +// Register agent services +import { agentServiceRegistry } from './AgentServiceRegistry' +import ClaudeCodeService from './claudecode' + +agentServiceRegistry.register('claude-code', new ClaudeCodeService()) + // Type definitions for service requests and responses export type { AgentEntity, AgentSessionEntity, CreateAgentRequest, UpdateAgentRequest } from '@types' export type { From d85d0a8cb41b72a426f1bac3497f8978819487eb Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:06:05 +0800 Subject: [PATCH 04/46] =?UTF-8?q?=E2=9C=A8=20feat(agents):=20add=20cherry-?= =?UTF-8?q?claw=20to=20agent-type=20dispatch=20points?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update BaseService.listMcpTools, BaseService.listSlashCommands, and SessionService.listSlashCommands to handle cherry-claw alongside claude-code. Signed-off-by: Vaayne --- src/main/services/agents/BaseService.ts | 4 ++-- src/main/services/agents/services/SessionService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/services/agents/BaseService.ts b/src/main/services/agents/BaseService.ts index f88edcc9a4d..4bb24629f9f 100644 --- a/src/main/services/agents/BaseService.ts +++ b/src/main/services/agents/BaseService.ts @@ -54,7 +54,7 @@ export abstract class BaseService { ): Promise<{ tools: Tool[]; legacyIdMap: Map }> { const tools: Tool[] = [] const legacyIdMap = new Map() - if (agentType === 'claude-code') { + if (agentType === 'claude-code' || agentType === 'cherry-claw') { tools.push(...builtinTools) } if (ids && ids.length > 0) { @@ -139,7 +139,7 @@ export abstract class BaseService { } public async listSlashCommands(agentType: AgentType): Promise { - if (agentType === 'claude-code') { + if (agentType === 'claude-code' || agentType === 'cherry-claw') { return builtinSlashCommands } return [] diff --git a/src/main/services/agents/services/SessionService.ts b/src/main/services/agents/services/SessionService.ts index 846e9bae2f3..9c3c5417632 100644 --- a/src/main/services/agents/services/SessionService.ts +++ b/src/main/services/agents/services/SessionService.ts @@ -37,7 +37,7 @@ export class SessionService extends BaseService { const commands: SlashCommand[] = [] // Add builtin slash commands - if (agentType === 'claude-code') { + if (agentType === 'claude-code' || agentType === 'cherry-claw') { commands.push(...builtinSlashCommands) } From 721a1d9b0ae7a0510d53e6f47f68e7381f82ab76 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:09:34 +0800 Subject: [PATCH 05/46] =?UTF-8?q?=E2=9C=A8=20feat(agents):=20implement=20C?= =?UTF-8?q?herryClawService=20with=20SoulReader=20and=20HeartbeatReader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SoulReader: reads soul.md from workspace with mtime-based caching - HeartbeatReader: reads heartbeat.md on demand with path traversal protection - CherryClawService: delegates to claude-code with soul-enhanced system prompt - Register cherry-claw in AgentServiceRegistry Signed-off-by: Vaayne --- .../agents/services/cherryclaw/heartbeat.ts | 31 +++++++++++ .../agents/services/cherryclaw/index.ts | 55 +++++++++++++++++++ .../agents/services/cherryclaw/soul.ts | 43 +++++++++++++++ src/main/services/agents/services/index.ts | 4 +- 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/main/services/agents/services/cherryclaw/heartbeat.ts create mode 100644 src/main/services/agents/services/cherryclaw/index.ts create mode 100644 src/main/services/agents/services/cherryclaw/soul.ts diff --git a/src/main/services/agents/services/cherryclaw/heartbeat.ts b/src/main/services/agents/services/cherryclaw/heartbeat.ts new file mode 100644 index 00000000000..f1ccd59fbae --- /dev/null +++ b/src/main/services/agents/services/cherryclaw/heartbeat.ts @@ -0,0 +1,31 @@ +import { readFile } from 'node:fs/promises' +import path from 'node:path' + +import { loggerService } from '@logger' + +const logger = loggerService.withContext('HeartbeatReader') + +export class HeartbeatReader { + async readHeartbeat(workspacePath: string, filename: string = 'heartbeat.md'): Promise { + const resolved = path.resolve(workspacePath, filename) + const normalizedWorkspace = path.resolve(workspacePath) + + if (!resolved.startsWith(normalizedWorkspace + path.sep) && resolved !== normalizedWorkspace) { + logger.warn(`Path traversal attempt blocked: ${filename}`) + return undefined + } + + try { + const content = await readFile(resolved, 'utf-8') + logger.info(`Read heartbeat file: ${resolved}`) + return content + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + logger.info(`Heartbeat file not found: ${resolved}`) + return undefined + } + logger.error(`Failed to read heartbeat file: ${resolved}`, error) + return undefined + } + } +} diff --git a/src/main/services/agents/services/cherryclaw/index.ts b/src/main/services/agents/services/cherryclaw/index.ts new file mode 100644 index 00000000000..3b00d4fdb0b --- /dev/null +++ b/src/main/services/agents/services/cherryclaw/index.ts @@ -0,0 +1,55 @@ +import { loggerService } from '@logger' +import type { CherryClawConfiguration, GetAgentSessionResponse } from '@types' + +import type { AgentServiceInterface, AgentStream, AgentThinkingOptions } from '../../interfaces/AgentStreamInterface' +import { agentServiceRegistry } from '../AgentServiceRegistry' +import { HeartbeatReader } from './heartbeat' +import { SoulReader } from './soul' + +const logger = loggerService.withContext('CherryClawService') + +/** + * CherryClawService — a Claude Code variant with soul-driven personality + * and scheduler-based autonomous operation. + * + * Delegates to ClaudeCodeService (via registry) with a soul-enhanced system prompt. + */ +export class CherryClawService implements AgentServiceInterface { + private soulReader = new SoulReader() + readonly heartbeatReader = new HeartbeatReader() + + async invoke( + prompt: string, + session: GetAgentSessionResponse, + abortController: AbortController, + lastAgentSessionId?: string, + thinkingOptions?: AgentThinkingOptions + ): Promise { + const config = (session.configuration ?? {}) as CherryClawConfiguration + const workspacePath = session.accessible_paths[0] + + // Build soul-enhanced session + let enhancedSession = session + + if (config.soul_enabled !== false && workspacePath) { + const soulContent = await this.soulReader.readSoul(workspacePath) + if (soulContent) { + logger.info('Prepending soul.md to instructions', { + workspacePath, + soulLength: soulContent.length + }) + const originalInstructions = session.instructions ?? '' + enhancedSession = { + ...session, + instructions: soulContent + '\n\n' + originalInstructions + } + } + } + + // Delegate to claude-code service (CherryClaw is a Claude Code variant) + const claudeCodeService = agentServiceRegistry.getService('claude-code') + return claudeCodeService.invoke(prompt, enhancedSession, abortController, lastAgentSessionId, thinkingOptions) + } +} + +export default CherryClawService diff --git a/src/main/services/agents/services/cherryclaw/soul.ts b/src/main/services/agents/services/cherryclaw/soul.ts new file mode 100644 index 00000000000..04513d7dc4a --- /dev/null +++ b/src/main/services/agents/services/cherryclaw/soul.ts @@ -0,0 +1,43 @@ +import { readFile, stat } from 'node:fs/promises' +import path from 'node:path' + +import { loggerService } from '@logger' + +const logger = loggerService.withContext('SoulReader') + +type CacheEntry = { + mtimeMs: number + content: string +} + +export class SoulReader { + private cache = new Map() + + async readSoul(workspacePath: string): Promise { + const soulPath = path.join(workspacePath, 'soul.md') + + let fileStat + try { + fileStat = await stat(soulPath) + } catch { + logger.info(`soul.md not found at ${soulPath}`) + return undefined + } + + const cached = this.cache.get(soulPath) + if (cached && cached.mtimeMs === fileStat.mtimeMs) { + logger.info(`Serving cached soul.md for ${workspacePath}`) + return cached.content + } + + try { + const content = await readFile(soulPath, 'utf-8') + this.cache.set(soulPath, { mtimeMs: fileStat.mtimeMs, content }) + logger.info(`Loaded soul.md from ${soulPath}`) + return content + } catch (error) { + logger.error(`Failed to read soul.md at ${soulPath}`, error) + return undefined + } + } +} diff --git a/src/main/services/agents/services/index.ts b/src/main/services/agents/services/index.ts index 483e3e835e0..ba2945d1cf9 100644 --- a/src/main/services/agents/services/index.ts +++ b/src/main/services/agents/services/index.ts @@ -18,11 +18,13 @@ export { sessionService } from './SessionService' // Agent service registry export { agentServiceRegistry } from './AgentServiceRegistry' -// Register agent services +// Register agent services — claude-code first (CherryClaw delegates to it at runtime) import { agentServiceRegistry } from './AgentServiceRegistry' +import { CherryClawService } from './cherryclaw' import ClaudeCodeService from './claudecode' agentServiceRegistry.register('claude-code', new ClaudeCodeService()) +agentServiceRegistry.register('cherry-claw', new CherryClawService()) // Type definitions for service requests and responses export type { AgentEntity, AgentSessionEntity, CreateAgentRequest, UpdateAgentRequest } from '@types' From 3836f7bb8d3bfe31de49613bca0d9f465a268069 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:11:00 +0800 Subject: [PATCH 06/46] =?UTF-8?q?=E2=9C=A8=20feat(agents):=20implement=20S?= =?UTF-8?q?chedulerService=20for=20CherryClaw=20autonomous=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cron-parser dependency for cron expression parsing - Implement SchedulerService with cron/interval/one-time scheduling - Per-agent tick guard prevents overlapping invocations - Pause after 3 consecutive errors - Restore schedulers on app boot via restoreSchedulers() - Deliver heartbeat content to most recent session on each tick Signed-off-by: Vaayne --- package.json | 1 + pnpm-lock.yaml | 36 ++- .../agents/services/SchedulerService.ts | 296 ++++++++++++++++++ 3 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 src/main/services/agents/services/SchedulerService.ts diff --git a/package.json b/package.json index 233f8045469..2301f1c3ff4 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@libsql/client": "0.14.0", "@napi-rs/system-ocr": "1.0.2", "@paymoapp/electron-shutdown-handler": "1.1.2", + "cron-parser": "^5.5.0", "express": "5.1.0", "font-list": "2.0.0", "graceful-fs": "4.2.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 188da65865c..5df5d205bc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: '@paymoapp/electron-shutdown-handler': specifier: 1.1.2 version: 1.1.2 + cron-parser: + specifier: ^5.5.0 + version: 5.5.0 express: specifier: 5.1.0 version: 5.1.0 @@ -205,7 +208,7 @@ importers: version: 5.6.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@ant-design/v5-patch-for-react-19': specifier: ^1.0.3 - version: 1.0.3(antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.0.3(antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@anthropic-ai/sdk': specifier: ^0.41.0 version: 0.41.0(encoding@0.1.13) @@ -631,7 +634,7 @@ importers: version: 6.0.103(zod@4.3.4) antd: specifier: 5.27.0 - version: 5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) archiver: specifier: ^7.0.1 version: 7.0.1 @@ -6366,6 +6369,10 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cron-parser@5.5.0: + resolution: {integrity: sha512-oML4lKUXxizYswqmxuOCpgFS8BNUJpIu6k/2HVHyaL8Ynnf3wdf9tkns0yRdJLSIjkJ+b0DXHMZEHGpMwjnPww==} + engines: {node: '>=18'} + cross-dirname@0.1.0: resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==} @@ -7705,11 +7712,11 @@ packages: glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -8706,6 +8713,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -12211,9 +12222,9 @@ snapshots: resize-observer-polyfill: 1.5.1 throttle-debounce: 5.0.2 - '@ant-design/v5-patch-for-react-19@1.0.3(antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@ant-design/v5-patch-for-react-19@1.0.3(antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - antd: 5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + antd: 5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -17464,7 +17475,7 @@ snapshots: ansis@4.2.0: {} - antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + antd@5.27.0(patch_hash=cdc383bd0d9b9fe0df2ce7b1f1d4ead200012b7f9517d9257b4ea0a5b324e243)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@ant-design/colors': 7.2.1 '@ant-design/cssinjs': 1.23.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -17496,7 +17507,7 @@ snapshots: rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-notification: 5.6.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-pagination: 5.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - rc-picker: 4.11.3(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + rc-picker: 4.11.3(dayjs@1.11.19)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-progress: 4.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-rate: 2.13.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-resize-observer: 1.4.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -18348,6 +18359,10 @@ snapshots: crelt@1.0.6: {} + cron-parser@5.5.0: + dependencies: + luxon: 3.7.2 + cross-dirname@0.1.0: optional: true @@ -20947,6 +20962,8 @@ snapshots: dependencies: react: 19.2.3 + luxon@3.7.2: {} + lz-string@1.5.0: {} mac-system-proxy@1.0.4: {} @@ -22701,7 +22718,7 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - rc-picker@4.11.3(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + rc-picker@4.11.3(dayjs@1.11.19)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -22713,6 +22730,7 @@ snapshots: react-dom: 19.2.3(react@19.2.3) optionalDependencies: dayjs: 1.11.19 + luxon: 3.7.2 moment: 2.30.1 rc-progress@4.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): diff --git a/src/main/services/agents/services/SchedulerService.ts b/src/main/services/agents/services/SchedulerService.ts new file mode 100644 index 00000000000..461e57da42a --- /dev/null +++ b/src/main/services/agents/services/SchedulerService.ts @@ -0,0 +1,296 @@ +import { loggerService } from '@logger' +import type { AgentEntity, CherryClawConfiguration, SchedulerType } from '@types' +import { parseExpression } from 'cron-parser' + +import { agentService } from './AgentService' +import { CherryClawService } from './cherryclaw' +import { sessionMessageService } from './SessionMessageService' +import { sessionService } from './SessionService' + +const logger = loggerService.withContext('SchedulerService') + +const MAX_CONSECUTIVE_ERRORS = 3 + +type SchedulerEntry = { + agentId: string + type: SchedulerType + timer: ReturnType | null + tickInProgress: boolean + consecutiveErrors: number + enabled: boolean + lastRun?: Date + nextRun?: Date +} + +export type SchedulerStatus = { + running: boolean + type: SchedulerType + tickInProgress: boolean + nextRun?: Date + lastRun?: Date + consecutiveErrors: number +} + +class SchedulerService { + private static instance: SchedulerService | null = null + private readonly schedulers = new Map() + private cherryClawService: CherryClawService | null = null + + static getInstance(): SchedulerService { + if (!SchedulerService.instance) { + SchedulerService.instance = new SchedulerService() + } + return SchedulerService.instance + } + + private getCherryClawService(): CherryClawService { + if (!this.cherryClawService) { + this.cherryClawService = new CherryClawService() + } + return this.cherryClawService + } + + startScheduler(agent: AgentEntity): void { + const config = (agent.configuration ?? {}) as CherryClawConfiguration + if (!config.scheduler_enabled || !config.scheduler_type) { + logger.info('Scheduler not enabled for agent', { agentId: agent.id }) + return + } + + // Stop existing scheduler for this agent if any + this.stopScheduler(agent.id) + + const entry: SchedulerEntry = { + agentId: agent.id, + type: config.scheduler_type, + timer: null, + tickInProgress: false, + consecutiveErrors: 0, + enabled: true + } + + this.schedulers.set(agent.id, entry) + this.scheduleNext(agent.id, config) + logger.info('Started scheduler', { agentId: agent.id, type: config.scheduler_type }) + } + + stopScheduler(agentId: string): void { + const entry = this.schedulers.get(agentId) + if (!entry) return + + if (entry.timer) { + clearTimeout(entry.timer) + entry.timer = null + } + entry.enabled = false + this.schedulers.delete(agentId) + logger.info('Stopped scheduler', { agentId }) + } + + stopAll(): void { + for (const agentId of this.schedulers.keys()) { + this.stopScheduler(agentId) + } + logger.info('All schedulers stopped') + } + + async restoreSchedulers(): Promise { + try { + const { agents } = await agentService.listAgents({ limit: 1000 }) + const clawAgents = agents.filter( + (a: AgentEntity) => a.type === 'cherry-claw' && (a.configuration as CherryClawConfiguration)?.scheduler_enabled + ) + + for (const agent of clawAgents) { + try { + this.startScheduler(agent) + } catch (error) { + logger.error('Failed to restore scheduler for agent', { + agentId: agent.id, + error: error instanceof Error ? error.message : String(error) + }) + } + } + + logger.info('Restored schedulers', { count: clawAgents.length }) + } catch (error) { + logger.error('Failed to restore schedulers', { + error: error instanceof Error ? error.message : String(error) + }) + } + } + + getSchedulerStatus(agentId: string): SchedulerStatus | null { + const entry = this.schedulers.get(agentId) + if (!entry) return null + + return { + running: entry.enabled, + type: entry.type, + tickInProgress: entry.tickInProgress, + nextRun: entry.nextRun, + lastRun: entry.lastRun, + consecutiveErrors: entry.consecutiveErrors + } + } + + isRunning(agentId: string): boolean { + return this.schedulers.has(agentId) && (this.schedulers.get(agentId)?.enabled ?? false) + } + + private scheduleNext(agentId: string, config: CherryClawConfiguration): void { + const entry = this.schedulers.get(agentId) + if (!entry || !entry.enabled) return + + const delayMs = this.computeDelayMs(config) + if (delayMs === null) { + logger.warn('Could not compute next delay for scheduler', { agentId, type: config.scheduler_type }) + return + } + + entry.nextRun = new Date(Date.now() + delayMs) + + entry.timer = setTimeout(() => { + this.onTick(agentId, config).catch((error) => { + logger.error('Tick handler error', { + agentId, + error: error instanceof Error ? error.message : String(error) + }) + }) + }, delayMs) + } + + private computeDelayMs(config: CherryClawConfiguration): number | null { + switch (config.scheduler_type) { + case 'cron': { + if (!config.scheduler_cron) return null + try { + const interval = parseExpression(config.scheduler_cron) + const next = interval.next().toDate() + return Math.max(next.getTime() - Date.now(), 1000) // at least 1s + } catch { + logger.warn('Invalid cron expression', { cron: config.scheduler_cron }) + return null + } + } + case 'interval': { + if (!config.scheduler_interval || config.scheduler_interval <= 0) return null + return config.scheduler_interval * 1000 // seconds to ms + } + case 'one-time': { + if (!config.scheduler_one_time_delay || config.scheduler_one_time_delay <= 0) return null + return config.scheduler_one_time_delay * 1000 // seconds to ms + } + default: + return null + } + } + + private async onTick(agentId: string, config: CherryClawConfiguration): Promise { + const entry = this.schedulers.get(agentId) + if (!entry || !entry.enabled) return + + // Tick guard — skip if previous tick still running + if (entry.tickInProgress) { + logger.warn('Skipping tick — previous tick still in progress', { agentId }) + // Reschedule for next interval (unless one-time) + if (config.scheduler_type !== 'one-time') { + this.scheduleNext(agentId, config) + } + return + } + + entry.tickInProgress = true + entry.lastRun = new Date() + + try { + // Read heartbeat content + const workspacePath = await this.getAgentWorkspacePath(agentId) + if (!workspacePath) { + logger.warn('No workspace path for agent, skipping tick', { agentId }) + return + } + + let heartbeatContent: string | undefined + if (config.heartbeat_enabled !== false) { + const clawService = this.getCherryClawService() + heartbeatContent = await clawService.heartbeatReader.readHeartbeat(workspacePath, config.heartbeat_file) + } + + if (!heartbeatContent) { + logger.warn('No heartbeat content, skipping tick', { agentId }) + return + } + + // Find most recent session by updated_at desc + const { sessions } = await sessionService.listSessions(agentId, { limit: 1 }) + if (!sessions || sessions.length === 0) { + logger.warn('No session found for agent, skipping tick', { agentId }) + return + } + + const session = await sessionService.getSession(agentId, sessions[0].id) + if (!session) { + logger.warn('Session not found', { agentId, sessionId: sessions[0].id }) + return + } + + // Send heartbeat as user message + logger.info('Delivering heartbeat to session', { + agentId, + sessionId: session.id, + contentLength: heartbeatContent.length + }) + + const abortController = new AbortController() + await sessionMessageService.createSessionMessage(session, { content: heartbeatContent }, abortController) + + // Update last run in agent configuration + await agentService.updateAgent(agentId, { + configuration: { + ...config, + scheduler_last_run: new Date().toISOString() + } + }) + + entry.consecutiveErrors = 0 + } catch (error) { + entry.consecutiveErrors++ + logger.error('Tick failed', { + agentId, + consecutiveErrors: entry.consecutiveErrors, + error: error instanceof Error ? error.message : String(error) + }) + + if (entry.consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + logger.warn('Pausing scheduler after consecutive errors', { + agentId, + errors: entry.consecutiveErrors + }) + this.stopScheduler(agentId) + return + } + } finally { + const currentEntry = this.schedulers.get(agentId) + if (currentEntry) { + currentEntry.tickInProgress = false + } + } + + // Schedule next tick (unless one-time) + if (config.scheduler_type !== 'one-time' && entry.enabled) { + this.scheduleNext(agentId, config) + } + } + + private async getAgentWorkspacePath(agentId: string): Promise { + try { + const agent = await agentService.getAgent(agentId) + return agent?.accessible_paths?.[0] + } catch { + return undefined + } + } +} + +export const schedulerService = SchedulerService.getInstance() From 65db4650d0a9be429c3710e0121a6f9e86ee4d1c Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 10 Mar 2026 01:13:13 +0800 Subject: [PATCH 07/46] =?UTF-8?q?=F0=9F=94=A7=20feat(agents):=20wire=20Sch?= =?UTF-8?q?edulerService=20into=20app=20lifecycle=20and=20agent=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore schedulers on app startup (after API server init) - Stop all schedulers on app quit - Stop scheduler when agent is deleted - Restart scheduler when cherry-claw agent is updated/patched Signed-off-by: Vaayne --- .../apiServer/routes/agents/handlers/agents.ts | 16 ++++++++++++++++ src/main/index.ts | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/src/main/apiServer/routes/agents/handlers/agents.ts b/src/main/apiServer/routes/agents/handlers/agents.ts index 53e5f9433e7..23269c70b9c 100644 --- a/src/main/apiServer/routes/agents/handlers/agents.ts +++ b/src/main/apiServer/routes/agents/handlers/agents.ts @@ -1,5 +1,6 @@ import { loggerService } from '@logger' import { AgentModelValidationError, agentService, sessionService } from '@main/services/agents' +import { schedulerService } from '@main/services/agents/services/SchedulerService' import type { ListAgentsResponse } from '@types' import { type ReplaceAgentRequest, type UpdateAgentRequest } from '@types' import type { Request, Response } from 'express' @@ -350,6 +351,12 @@ export const updateAgent = async (req: Request, res: Response): Promise }) } + // Restart scheduler if this is a cherry-claw agent with scheduler config changes + if (agent.type === 'cherry-claw') { + schedulerService.stopScheduler(agentId) + schedulerService.startScheduler(agent) + } + logger.info('Agent patched', { agentId }) return res.json(agent) } catch (error: any) { @@ -556,6 +569,9 @@ export const deleteAgent = async (req: Request, res: Response): Promise Date: Tue, 10 Mar 2026 01:14:43 +0800 Subject: [PATCH 08/46] =?UTF-8?q?=E2=9C=A8=20feat(ui):=20add=20agent=20typ?= =?UTF-8?q?e=20selector=20to=20creation=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add type dropdown (Claude Code / CherryClaw) in creation modal - Auto-apply CherryClaw defaults when cherry-claw type is selected - Show bypassPermissions warning for CherryClaw agents - Type selection only shown during creation (not editing) Signed-off-by: Vaayne --- .../components/Popups/agent/AgentModal.tsx | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/Popups/agent/AgentModal.tsx b/src/renderer/src/components/Popups/agent/AgentModal.tsx index b72c23e6a43..13b32f259b5 100644 --- a/src/renderer/src/components/Popups/agent/AgentModal.tsx +++ b/src/renderer/src/components/Popups/agent/AgentModal.tsx @@ -2,7 +2,7 @@ import { loggerService } from '@logger' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { HelpTooltip } from '@renderer/components/TooltipIcons' import { TopView } from '@renderer/components/TopView' -import { permissionModeCards } from '@renderer/config/agent' +import { DEFAULT_CHERRY_CLAW_CONFIG, permissionModeCards } from '@renderer/config/agent' import { isWin } from '@renderer/config/constant' import { useAgents } from '@renderer/hooks/agents/useAgents' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' @@ -10,6 +10,7 @@ import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAg import type { AddAgentForm, AgentEntity, + AgentType, ApiModel, BaseAgentForm, PermissionMode, @@ -122,6 +123,26 @@ const PopupContainer: React.FC = ({ agent, afterSubmit, resolve }) => { } }, [checkGitBash]) + const onTypeChange = useCallback((value: AgentType) => { + setForm((prev) => { + if (value === 'cherry-claw') { + return { + ...prev, + type: value, + configuration: { + ...AgentConfigurationSchema.parse(prev.configuration ?? {}), + ...DEFAULT_CHERRY_CLAW_CONFIG.configuration + } + } + } + return { + ...prev, + type: value, + configuration: AgentConfigurationSchema.parse(prev.configuration ?? {}) + } + }) + }, []) + const onPermissionModeChange = useCallback((value: PermissionMode) => { setForm((prev) => { const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {}) @@ -338,8 +359,28 @@ const PopupContainer: React.FC = ({ agent, afterSubmit, resolve }) => { + {!isEditing(agent) && ( + + + + + )} + {form.type === 'cherry-claw' && ( + + {t( + 'agent.cherryClaw.warning.bypassPermissions', + 'CherryClaw agents run with Full Auto Mode by default. All tools execute without asking for approval.' + )} + + )} +