Skip to content

Commit 88bcff3

Browse files
authored
fix(skills): repair butler endpoint drift + add cron scheduling (#550)
## Why Routine drift audit of the AionUi Butler assistant (`aionui-assistant`) against current `aioncore` source. Two of its documented endpoints were dead (would silently fail when an agent followed the skill), and the cron backend gained a full REST surface the butler wasn't using. ## What **Breaking fixes (`aionui-config`)** - `GET /api/agents` → removed; the path 404s (`legacy_agents_endpoint_is_not_found`). The live engine list is `GET /api/agents/management`, returning `AgentManagementRow`s with `installed`/`status` instead of the old `available`/`handshake` shape. `handshake`/`available` now documented as living on `POST /api/agents/refresh`. - `GET /api/skills/builtin-auto` → removed; the route 405s (`builtin_auto_skill_list_get_is_not_registered`). `GET /api/skills` already lists every skill with its source. **New capability (`aionui-config`)** - Added a "Scheduled tasks (cron)" section: natural-language schedule → `POST /api/cron/jobs`, with the three `schedule` shapes (`at`/`every`/`cron`), required/optional fields, and the list/get/update/run-now/delete endpoints. Captured the non-obvious contract (5-field crontab auto-prepends seconds, `created_by` is `user|agent`, `conversation_id` always required, `agent_config.assistant_id` required in practice, `deny_unknown_fields`). - Removed the stale "cron is out of scope" note; added a cron verification step and cron to the frontmatter description for auto-trigger. **Doc fix (`aionui-troubleshooting`)** - Corrected the "no REST API for crons" claim — `/api/cron/jobs` exists; SQLite stays the diagnostic read path. **Rule (`aionui-assistant`, all three locales)** - Removed the "I don't create / configure scheduled tasks" disclaimer; advertised scheduled-task creation in the Configuration intro and the skills table. zh-CN uses full-width punctuation. ## How verified Every change traced to source before editing: - `crates/aionui-ai-agent/src/routes/agent.rs`, `registry.rs::list_management_rows` (route table + field projection) - `crates/aionui-api-types/src/agent_discovery.rs` (`AgentManagementRow` vs `AgentMetadata`) - `crates/aionui-extension/src/skill_routes.rs` (skill route table, the 405 test) - `crates/aionui-cron/src/{routes.rs,service.rs,scheduler.rs,types.rs}` + `crates/aionui-api-types/src/cron.rs` (cron contract) - The 404/405 assertions in `crates/aionui-app/tests/acp_e2e.rs` and `skill_routes.rs` ## Deploy path Asset-only change — no `.rs` modifications. Ships via `include_dir!` on the next `aioncore` binary build.
1 parent 2c7cfe8 commit 88bcff3

5 files changed

Lines changed: 120 additions & 25 deletions

File tree

crates/aionui-app/assets/builtin-assistants/rules/aionui-assistant.en-US.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ Be proactive, helpful, and keep things easy for the user.
1919
- Configure MCP servers
2020
- Add an LLM model / API key, switch the default model
2121
- Change UI settings (language, theme, font size, zoom, notifications)
22+
- Schedule recurring or one-off tasks ("every morning at 9", "remind me in 2 hours")
2223

2324
**Troubleshooting (diagnose problems)**
2425

2526
- A conversation is stuck or errored
2627
- A model / provider call is failing
27-
- Why a scheduled (cron) task didn't run (I can diagnose this, but I don't create / configure scheduled tasks)
28+
- Why a scheduled (cron) task didn't run
2829
- An MCP server has no tools, a team member is hung
2930

3031
**Remote access (use it from elsewhere)**
@@ -40,7 +41,7 @@ What would you like me to help with?"
4041

4142
| Skill | Purpose | Nature |
4243
| --- | --- | --- |
43-
| **aionui-config** | Create/edit assistants, import & attach skills, configure MCP, add LLM providers & API keys, change app/UI settings | **Write** (affects the live app) |
44+
| **aionui-config** | Create/edit assistants, import & attach skills, configure MCP, add LLM providers & API keys, change app/UI settings, create & manage scheduled tasks | **Write** (affects the live app) |
4445
| **aionui-troubleshooting** | Inspect conversations/runtime, read aioncore logs, check provider health, cron / team / MCP status | **Read-only** diagnosis |
4546
| **aionui-webui-public** | Set up remote access to the local AionUi and produce an external access link | **Execute** (runs commands on the user's machine, opens a connection) |
4647

crates/aionui-app/assets/builtin-assistants/rules/aionui-assistant.ru-RU.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
- Настройка серверов MCP
2020
- Добавление модели LLM / API-ключа, смена модели по умолчанию
2121
- Изменение настроек интерфейса (язык, тема, размер шрифта, масштаб, уведомления)
22+
- Планирование периодических или разовых задач («каждое утро в 9», «напомни через 2 часа»)
2223

2324
**Диагностика (найду проблему)**
2425

2526
- Разговор завис или выдал ошибку
2627
- Сбой вызова модели / провайдера
27-
- Почему запланированная задача (cron) не выполнилась (я могу это диагностировать, но не создаю / не настраиваю запланированные задачи)
28+
- Почему запланированная задача (cron) не выполнилась
2829
- У сервера MCP нет инструментов, участник команды завис
2930

3031
**Удалённый доступ (чтобы пользоваться откуда угодно)**
@@ -40,7 +41,7 @@
4041

4142
| Навык | Назначение | Характер |
4243
| --- | --- | --- |
43-
| **aionui-config** | Создание/редактирование ассистентов, импорт и подключение навыков, настройка MCP, добавление провайдеров LLM и API-ключей, изменение настроек приложения/интерфейса | **Запись** (влияет на работающее приложение) |
44+
| **aionui-config** | Создание/редактирование ассистентов, импорт и подключение навыков, настройка MCP, добавление провайдеров LLM и API-ключей, изменение настроек приложения/интерфейса, создание и управление запланированными задачами | **Запись** (влияет на работающее приложение) |
4445
| **aionui-troubleshooting** | Просмотр разговоров/состояния выполнения, чтение логов aioncore, проверка здоровья провайдеров, состояние cron / команд / MCP | **Только чтение**, диагностика |
4546
| **aionui-webui-public** | Настроить удалённый доступ к локальному AionUi и выдать внешнюю ссылку доступа | **Выполнение** (запускает команды на машине пользователя, открывает соединение) |
4647

crates/aionui-app/assets/builtin-assistants/rules/aionui-assistant.zh-CN.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
- 配置 MCP 服务器
2020
- 添加 LLM 模型 / API Key,切换默认模型
2121
- 调整界面设置(语言、主题、字号、缩放、通知)
22+
- 安排定时任务(如"每天早上9点""2小时后提醒我")
2223

2324
**排查类(帮你诊断)**
2425

2526
- 会话卡住、报错
2627
- 模型 / Provider 调用失败
27-
- 定时任务(cron)为什么没执行(我能诊断,但不负责创建/配置定时任务)
28+
- 定时任务(cron)为什么没执行
2829
- MCP 服务器没有工具、团队成员卡住
2930

3031
**远程访问(帮你在外面也能用)**
@@ -40,7 +41,7 @@
4041

4142
| 技能 | 用途 | 性质 |
4243
| --- | --- | --- |
43-
| **aionui-config** | 创建/编辑助手、导入并绑定技能、配置 MCP、添加 LLM Provider 与 API Key、改应用/界面设置 | ****(会改动用户的实时应用) |
44+
| **aionui-config** | 创建/编辑助手、导入并绑定技能、配置 MCP、添加 LLM Provider 与 API Key、改应用/界面设置、创建与管理定时任务 | ****(会改动用户的实时应用) |
4445
| **aionui-troubleshooting** | 查会话/运行状态、读 aioncore 日志、查 Provider 健康、cron / team / MCP 状态 | **只读**诊断 |
4546
| **aionui-webui-public** | 把本机 AionUi 配置成可远程访问,生成外网访问链接 | **执行**(在用户机器上跑命令、建连接) |
4647

crates/aionui-app/assets/builtin-skills/aionui-config/SKILL.md

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: aionui-config
33
description: >-
4-
Configure AionUi itself through its backend API — create and edit assistants (name, avatar, system prompt, quick-start prompts, engine), import and attach skills, manage MCP servers, configure LLM providers (add/edit a model endpoint, set the API key, fetch the model list, pick the default model), and change app/UI settings (language, theme, font size, zoom, notifications). Use when the user wants you to set up an AionUi assistant, sink a skill into AionUi's skill registry, attach skills to an assistant, change an assistant's avatar or system prompt, add or configure an MCP server, add an LLM/model provider or API key, switch the default model, change the theme or language, or otherwise configure their AionUi installation. This is "Agent-assisted AionUi configuration": you act on the user's behalf via the local backend.
4+
Configure AionUi itself through its backend API — create and edit assistants (name, avatar, system prompt, quick-start prompts, engine), import and attach skills, manage MCP servers, configure LLM providers (add/edit a model endpoint, set the API key, fetch the model list, pick the default model), change app/UI settings (language, theme, font size, zoom, notifications), and create or manage scheduled tasks (cron jobs) from a natural-language schedule. Use when the user wants you to set up an AionUi assistant, sink a skill into AionUi's skill registry, attach skills to an assistant, change an assistant's avatar or system prompt, add or configure an MCP server, add an LLM/model provider or API key, switch the default model, change the theme or language, schedule a recurring or one-off task ("every morning at 9", "remind me in 2 hours", "run this daily"), or otherwise configure their AionUi installation. This is "Agent-assisted AionUi configuration": you act on the user's behalf via the local backend.
55
---
66

77
> **⚠️ Platform note — read before running any command.** The shell snippets in this skill are written for **macOS / Linux** (bash/zsh). Always check which OS you are on first. On **Windows** do **not** run them verbatim — the underlying tool/CLI commands are usually cross-platform, but the surrounding shell syntax is not. Translate it to PowerShell before running:
@@ -265,7 +265,6 @@ Three sources: `builtin` (`~/.aionui/builtin-skills/`), `custom`
265265
```bash
266266
python3 scripts/aionui_api.py get /api/skills
267267
python3 scripts/aionui_api.py get /api/skills/paths # where skills live on disk
268-
python3 scripts/aionui_api.py get /api/skills/builtin-auto # auto-injected builtin skills
269268
python3 scripts/aionui_api.py post /api/skills/info '{"skill_path":"/abs/path/to/skill-folder"}' # read a SKILL.md's name/description WITHOUT importing
270269
```
271270

@@ -534,15 +533,106 @@ python3 scripts/aionui_api.py put /api/settings/client '{"ui.zoomFactor": 1.0}'
534533

535534
## Engines (agents)
536535

537-
`GET /api/agents` lists the available engines (`aionrs`, `claude`, `codex`, …).
538-
Each entry carries `enabled` (toggled on), `available` (installed & reachable),
539-
`team_capable` (can run in a team), and a `handshake` object describing what the
540-
engine supports — `agent_capabilities`, `auth_methods`, `config_options`,
541-
`available_modes`, `available_models`, `available_commands`. Check `available`
542-
before binding an assistant to that engine (via its `agent_id` — see *Picking the
543-
engine* above), and inspect `handshake` to see which models/modes that engine
544-
offers. `POST /api/agents/refresh` re-scans custom
545-
agents.
536+
`GET /api/agents/management` lists the available engines (`aionrs`, `claude`,
537+
`codex`, …). There is **no** bare `GET /api/agents` — that path 404s; always use
538+
the `/management` sub-path. Each row carries `id`, `enabled` (toggled on),
539+
`installed` (spawn command resolvable on `$PATH`), `team_capable` (can run in a
540+
team), `backend`, `agent_type`, and a `status` of `online` / `offline` /
541+
`missing`. Check `installed` (and `status`) before binding an assistant to that
542+
engine (via its `agent_id` — see *Picking the engine* above).
543+
544+
> The management row does **not** include the engine `handshake` (its
545+
> `agent_capabilities` / `auth_methods` / `config_options` / `available_modes` /
546+
> `available_models` / `available_commands`) — those live on the fuller agent
547+
> metadata returned by `POST /api/agents/refresh`, which re-scans custom agents
548+
> and returns each agent's `available` + `handshake`. Use `refresh` when you need
549+
> the modes/models an engine offers; use `management` for the at-a-glance
550+
> enabled/installed/status list.
551+
552+
---
553+
554+
## Scheduled tasks (cron)
555+
556+
Create and manage scheduled tasks ("run this every morning at 9", "remind me in
557+
two hours", "every 30 minutes do X") over the REST API — `/api/cron/*` is a
558+
full, verified CRUD surface. Translate the user's natural-language schedule into
559+
one of three `schedule` shapes, then `POST /api/cron/jobs`.
560+
561+
### The schedule (`schedule` is a tagged union — the `kind` field picks the shape)
562+
563+
| Natural language | `schedule` body |
564+
| --- | --- |
565+
| "at 3pm today / on this exact date" (one-shot) | `{"kind":"at","at_ms":<unix-ms>}` |
566+
| "every 30 minutes / every 2 hours" (fixed interval) | `{"kind":"every","every_ms":<ms>}` |
567+
| "every day at 9am / every Monday" (calendar) | `{"kind":"cron","expr":"0 9 * * *","tz":"Asia/Shanghai"}` |
568+
569+
- **`cron.expr` takes a standard 5-field crontab** (`min hour day month weekday`).
570+
The backend auto-prepends the seconds field, so `0 9 * * *` means 09:00 daily.
571+
(A 6-field `sec min hour day month weekday` is also accepted as-is.)
572+
- **`cron.tz`** is an IANA timezone name (`Asia/Shanghai`, `America/New_York`,
573+
`UTC`). Omit it and the expression runs in UTC — always set it to the user's
574+
zone for "9am" to mean their 9am.
575+
- **`every.every_ms`** must be `> 0`. There is no documented lower bound, but be
576+
sensible — don't schedule a sub-minute loop unless asked.
577+
- **`at.at_ms`** is a Unix timestamp in **milliseconds**. A past time is accepted
578+
by the API but will not run, so compute it from "now" in the user's zone.
579+
- An optional `description` can go inside any schedule variant for a
580+
human-readable label.
581+
582+
### Required fields on `POST /api/cron/jobs`
583+
584+
```bash
585+
python3 scripts/aionui_api.py post /api/cron/jobs '{
586+
"name": "每日早报",
587+
"schedule": {"kind": "cron", "expr": "0 9 * * *", "tz": "Asia/Shanghai"},
588+
"message": "总结今天的科技新闻",
589+
"conversation_id": "<conv-id>",
590+
"created_by": "agent",
591+
"execution_mode": "new_conversation",
592+
"agent_config": {"name": "AionUi Butler", "assistant_id": "<assistant-id>"}
593+
}'
594+
```
595+
596+
| Field | Required | Meaning |
597+
| --- | --- | --- |
598+
| `name` || display name of the task |
599+
| `schedule` || one of the three shapes above |
600+
| `conversation_id` || the conversation the task is tied to — get one from `GET /api/conversations` (or create one). Even `new_conversation` jobs need this set |
601+
| `created_by` || `"agent"` when you create it on the user's behalf, `"user"` for a user-initiated one. **Only these two values** |
602+
| `message` (or `prompt`) || the instruction sent on each run. `message` wins if both are given; with neither, the run sends an empty prompt |
603+
| `execution_mode` || `"existing"` (default) reuses `conversation_id` every run; `"new_conversation"` spins up a fresh conversation each run |
604+
| `agent_config` || which assistant runs the task. **In practice required for a new job**: omit it and the API 400s with *"assistant_id is required for new cron jobs"*. Pass `{"name":"<label>","assistant_id":"<id>"}` |
605+
| `description` || optional longer description |
606+
607+
> `agent_config` is strict (`deny_unknown_fields`): only `name`, `assistant_id`,
608+
> `cli_path`, `mode`, `model_id`, `model`, `config_options`, `workspace` are
609+
> accepted. Legacy keys `backend`, `agent_type`, `custom_agent_id`, `is_preset`
610+
> are **rejected** — don't send them. Get the `assistant_id` from
611+
> `GET /api/assistants`.
612+
613+
The response is the created job (HTTP 201) with its generated `id` (prefixed
614+
`cron_…`), resolved `agent_type`, and a `state` block (`next_run_at_ms`,
615+
`run_count`, …).
616+
617+
### List / inspect / change / run / delete
618+
619+
```bash
620+
python3 scripts/aionui_api.py get /api/cron/jobs # all jobs
621+
python3 scripts/aionui_api.py get "/api/cron/jobs?conversation_id=<id>" # jobs for one conversation
622+
python3 scripts/aionui_api.py get /api/cron/jobs/<id> # one job
623+
python3 scripts/aionui_api.py put /api/cron/jobs/<id> '{"enabled": false}' # partial update (pause)
624+
python3 scripts/aionui_api.py post /api/cron/jobs/<id>/run # run it once right now
625+
python3 scripts/aionui_api.py delete /api/cron/jobs/<id> # remove it
626+
```
627+
628+
`PUT` is a partial update — send only what changes (`name`, `description`,
629+
`enabled`, `schedule`, `message`, `execution_mode`, `agent_config`,
630+
`conversation_title`, `max_retries`). Read the job back to confirm its
631+
`schedule` and `state.next_run_at_ms` after any change.
632+
633+
> Note: an `existing`-mode job can't have its assistant changed after creation
634+
> (`agent_config` on update is rejected for ongoing-conversation jobs) — that's
635+
> by design, the ongoing conversation keeps its original assistant.
546636
547637
---
548638

@@ -557,7 +647,9 @@ After a configuration task, confirm with reads:
557647
5. MCP server in `get /api/mcp/servers`, enabled, right transport?
558648
6. Provider in `get /api/providers`, enabled, right `models`? (redact the key)
559649
7. Settings changed? `get /api/settings/client` shows the new value.
560-
8. Tell the user to refresh / reopen the AionUi view to see changes.
650+
8. Scheduled task created? `get /api/cron/jobs` lists it, `enabled: true`, with
651+
the expected `schedule` and a non-null `state.next_run_at_ms`.
652+
9. Tell the user to refresh / reopen the AionUi view to see changes.
561653

562654
## Out of scope (handled elsewhere)
563655

@@ -568,11 +660,9 @@ API here:
568660
- **Teams** (`/api/teams/*`) — create Teams through the Team UI or REST API.
569661
Once a Team session is active, Team agents use the `team_*` MCP tools
570662
provided by the per-Team `aionui-team` server.
571-
- **Cron / scheduled jobs** (`/api/cron/*`) — created and managed through their
572-
own flow (scheduling tools / the AionUi cron UI), not this skill.
573663

574664
This skill stays focused on *configuration*: assistants, skills, MCP servers,
575-
LLM providers, and app settings.
665+
LLM providers, app settings, and scheduled tasks.
576666

577667
## Not yet covered
578668

crates/aionui-app/assets/builtin-skills/aionui-troubleshooting/SKILL.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,11 @@ python3 scripts/aion_diag.py logs --errors --lines 100
140140
python3 scripts/aion_diag.py crons
141141
```
142142

143-
There is **no REST API for crons** — this reads the `cron_jobs` table from the
144-
SQLite store directly (read-only). It surfaces a `failing` list (jobs whose
145-
`last_status` is `error` or `missed`) plus every job's `schedule_*`,
143+
This reads the `cron_jobs` table from the SQLite store directly (read-only). A
144+
REST API for crons does exist (`/api/cron/jobs`, used by the `aionui-config`
145+
skill to *create/manage* jobs), but for diagnosis the table read is more direct
146+
and surfaces the run-state columns in one shot. It surfaces a `failing` list
147+
(jobs whose `last_status` is `error` or `missed`) plus every job's `schedule_*`,
146148
`last_status`, `last_error`, `next_run_at`, `last_run_at`, `run_count`,
147149
`retry_count`. Check `enabled`, compare `next_run_at` to now, and read
148150
`last_error` for failed jobs.
@@ -203,7 +205,7 @@ python3 scripts/aion_diag.py logs [--lines N] [--errors] [--conv <id>]
203205
| Conversation list + runtime state | `GET /api/conversations[/{id}]` | REST |
204206
| Conversation messages / errors | `messages` table (by `conversation_id`) | SQLite (read-only) |
205207
| LLM provider health | `GET /api/providers``model_health` | REST (api_key redacted) |
206-
| Scheduled jobs | `cron_jobs` table | SQLite (no REST API) |
208+
| Scheduled jobs | `cron_jobs` table (REST `/api/cron/jobs` exists too) | SQLite (read-only) |
207209
| Teams + members | `GET /api/teams` | REST |
208210
| MCP servers | `GET /api/mcp/servers` | REST |
209211
| Logs | `*.aioncore.log` in `--log-dir` | File tail |

0 commit comments

Comments
 (0)