Skip to content

Commit 41c2c94

Browse files
authored
fix(skills): correct AionUi Butler skill drift against current backend (#557)
## Why Nightly drift audit of the built-in **AionUi Butler** (`aionui-assistant`) skills against current `aioncore` source found several concrete claims that had drifted from the backend — some would silently fail (400/404) or misdirect the agent, one was an actual logic bug in a diagnostic script. Each finding was confirmed against `file:line` before editing. ## What (grouped as committed, per concern) **`fix(skills)`: aionui-config** — breaking corrections - `/api/skills/scan` body field `path` → **`folder_path`**; `POST /api/skills/external-paths` requires **both `name` and `path`** (`aionui-api-types/src/skill.rs`). - Removed non-existent `POST /api/skills/import-symlink`; bare `/api/skills/import` already handles dir/parent/zip. Documented the real reverse op `/api/skills/export-symlink` (`aionui-extension/src/skill_routes.rs`). - MCP transport table dropped `streamable_http` — REST layer accepts only `stdio`/`sse`/`http` (`McpTransport`, `mcp.rs`). - Engine example read `.engine.agent_id` off the flat LIST response (`AssistantResponse` is flat; only the detail read nests `engine`). - `defaults` is never `null` on read (defaults to `{"mode":"auto"}`). - Accuracy nits: detect-protocol has no `platform` (`preferred_protocol` is an enum); fetch-models *does* require `platform`; PUT bodies ignore stray `id`; `PUT /api/settings/client` returns no data; `/api/agents/management` actually carries modes/models/config_options. - New capabilities surfaced: `cron` skill source; per-cron-job `/skill` + `/conversations`; `generated` assistant source; assistant-skill trio; MCP single-get/bulk-import/agent-configs; skills import-history/limits. **`fix(skills)`: aionui-troubleshooting** — script logic bug - `aion_diag.py` keyed the stuck hint off `task_status == "running"`, but that field (pending/running/finished) is distinct from the runtime `state` (idle/starting/running/…) the hint text describes — could misfire or miss a hang. Now keys off `state`, with a separate hint for `waiting_confirmation` (blocked on approval, not stuck). - Fixed the stale "No REST API for crons" comment; documented extra runtime fields and `model_health.error`. **`docs(skills)`: aionui-webui-public** - Noted the `/api/webui/*` credential endpoints require local mode (403 as standalone server) — `ensure_local_mode()` in `aionui-auth/src/routes.rs`. ## How verified - Every drift re-confirmed against `file:line` in `aioncore` source before editing. - `cargo fmt --check` ✅ and `cargo clippy` ✅ pass. - Full `cargo nextest` run: **1331 passed, 1 failed** — the one failure, `connection_test_e2e::t8_1_bedrock_invalid_credentials`, is **pre-existing and environmental** (fails identically on clean `main`; it sends invalid AWS Bedrock creds expecting a 422 but gets 200 without live AWS reachability). Unrelated to this change, which is asset-only. ## Ship path Assets are compiled in via `include_dir!` — **no `.rs` changes**, so this ships to users on the next `aioncore` binary build.
1 parent 236217d commit 41c2c94

4 files changed

Lines changed: 117 additions & 58 deletions

File tree

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

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ An assistant has two parts stored separately:
7676
user_file`), written via a dedicated endpoint. Creating an assistant does
7777
NOT set its system prompt — that's a second call.
7878

79-
Assistant `source` is `builtin` (shipped with the app, limited edits) or
80-
`user` (custom, fully editable). Custom IDs look like `custom-<digits>-<hex>`.
79+
Assistant `source` is `builtin` (shipped with the app, limited edits), `user`
80+
(custom, fully editable), or `generated` (auto-materialized from an online ACP
81+
agent — identity fields locked, not deletable). Custom IDs look like
82+
`custom-<digits>-<hex>`.
8183

8284
### List / inspect
8385

@@ -135,11 +137,13 @@ installed agent's id — not a friendly name like `"claude"`. Read the available
135137
agents first and copy the id you want:
136138

137139
```bash
140+
# the LIST response is flat — each row carries agent_id + agent directly:
138141
python3 scripts/aionui_api.py get /api/assistants
139-
# look at the `engine` block of any existing assistant, e.g.
140-
# "engine": {"agent_id": "2d23ff1c", "agent": {"type": "acp", "acp_backend": "claude"}}
141-
# reuse that agent_id for a new assistant on the same engine:
142-
python3 scripts/aionui_api.py put /api/assistants/<id> '{"id":"<id>","agent_id":"2d23ff1c"}'
142+
# {"id": "...", "agent_id": "2d23ff1c", "agent": {"type": "acp", "acp_backend": "claude"}, ...}
143+
# (only the single-assistant detail read nests these under an `engine` block:
144+
# GET /api/assistants/<id>?locale=en -> "engine": {"agent_id": "...", "agent": {...}})
145+
# reuse an existing agent_id for a new assistant on the same engine:
146+
python3 scripts/aionui_api.py put /api/assistants/<id> '{"agent_id":"2d23ff1c"}'
143147
```
144148

145149
> If you omit `agent_id` on create, the backend does NOT default to a CLI engine:
@@ -176,7 +180,6 @@ modes the backend accepts. The `value` is what `fixed` locks to:
176180
177181
```bash
178182
python3 scripts/aionui_api.py put /api/assistants/<id> '{
179-
"id": "<id>",
180183
"defaults": {
181184
"model": {"mode": "fixed", "value": "gemini-2.5-pro"},
182185
"permission": {"mode": "fixed", "value": "plan"},
@@ -187,13 +190,14 @@ python3 scripts/aionui_api.py put /api/assistants/<id> '{
187190
```
188191

189192
> Verified end-to-end: the backend stores all four entries verbatim and returns
190-
> them on the `?locale=` detail read. A brand-new assistant has `defaults: null`
191-
> until you set them.
193+
> them on the `?locale=` detail read. On read, `defaults` is always present with
194+
> all four entries — a brand-new assistant has each set to `{"mode":"auto"}` (no
195+
> `value`), never `null`. Lock one by sending `{"mode":"fixed","value":...}`.
192196
193197
### Update
194198

195-
`PUT /api/assistants/<id>` with `{"id": "<id>", ...fields to change}`. Send only
196-
the fields you want to change.
199+
`PUT /api/assistants/<id>`, sending only the fields you want to change. The `id`
200+
comes from the URL path — a body `id`, if sent, is ignored.
197201

198202
### Set the system prompt (rules)
199203

@@ -216,14 +220,20 @@ python3 scripts/aionui_api.py post /api/skills/assistant-rule/read '{"assistant_
216220
For multi-line / long prompts, write the text to a temp file and build the JSON
217221
body in Python rather than inlining a giant shell string.
218222

223+
> To wipe an assistant's rule across all locales, `DELETE
224+
> /api/skills/assistant-rule/<assistant-id>`. A parallel trio exists for
225+
> per-assistant **skill** content (distinct from the shared registry):
226+
> `POST /api/skills/assistant-skill/{read,write}` (same body shape as the rule
227+
> endpoints) and `DELETE /api/skills/assistant-skill/<assistant-id>`.
228+
219229
### Avatar
220230

221231
The `avatar` field accepts an emoji (`"📋"`), an image URL, a `data:` URI, or an
222232
absolute local path. A self-contained inline SVG `data:` URI is a good default —
223233
no external dependency, renders offline:
224234

225235
```bash
226-
python3 scripts/aionui_api.py put /api/assistants/<id> '{"id":"<id>","avatar":"data:image/svg+xml;base64,<...>"}'
236+
python3 scripts/aionui_api.py put /api/assistants/<id> '{"avatar":"data:image/svg+xml;base64,<...>"}'
227237
```
228238

229239
> `GET /api/assistants/<id>/avatar` also serves the raw avatar binary (Content-Type
@@ -239,7 +249,8 @@ team picker without deleting it.
239249
### Delete
240250

241251
`DELETE /api/assistants/<id>` — only `source: user` assistants can be deleted.
242-
Builtins can only be disabled.
252+
`builtin` and `generated` assistants can only be disabled (check the `deletable`
253+
flag on the detail read).
243254

244255
### Bulk import
245256

@@ -257,8 +268,9 @@ A skill is a folder containing a `SKILL.md` (YAML frontmatter `name` +
257268
`description`, then instruction body). The `description` decides when the agent
258269
auto-triggers the skill, so write it carefully.
259270

260-
Three sources: `builtin` (`~/.aionui/builtin-skills/`), `custom`
261-
(`~/.aionui/skills/`), `extension` (external, symlinked).
271+
Four sources: `builtin` (`~/.aionui/builtin-skills/`), `custom`
272+
(`~/.aionui/skills/`), `cron` (`~/.aionui/cron/skills/`, per-scheduled-task
273+
skills), `extension` (external, symlinked).
262274

263275
### List / inspect the registry
264276

@@ -270,18 +282,23 @@ python3 scripts/aionui_api.py post /api/skills/info '{"skill_path":"/abs/path/to
270282

271283
### Import a skill into the registry
272284

273-
Two ways to install a skill — pick by whether you want a copy or a live link:
285+
`POST /api/skills/import` copies the skill(s) into the user skills dir and
286+
registers them. The one endpoint handles all three source shapes: a single skill
287+
folder, a PARENT folder containing many skills, or a `.zip` package.
274288

275289
```bash
276-
# copy the folder into the user skills dir and register it
277-
python3 scripts/aionui_api.py post /api/skills/import '{"skill_path":"/abs/path/to/skill-folder"}'
278-
279-
# symlink instead of copy — good for a skill you keep editing in an external repo.
280-
# import-symlink (NOT bare import) is also what accepts a PARENT folder of many
281-
# skills, or a .zip package.
282-
python3 scripts/aionui_api.py post /api/skills/import-symlink '{"skill_path":"/abs/path/to/skill-or-parent-or-zip"}'
290+
python3 scripts/aionui_api.py post /api/skills/import '{"skill_path":"/abs/path/to/skill-or-parent-or-zip"}'
291+
python3 scripts/aionui_api.py get /api/skills/import-limits # server-side max file/total byte caps
292+
python3 scripts/aionui_api.py get /api/skills/import-history # recent import records
283293
```
284294

295+
> For external skills you keep editing in place (registered without copying), see
296+
> "Discover & manage skill sources" below (`external-paths`) — that's how a live,
297+
> non-copied source is wired in. There is no `import-symlink` endpoint; the
298+
> reverse operation, `POST /api/skills/export-symlink`
299+
> (`{"skill_path":"...","target_dir":"..."}`), symlinks an already-installed skill
300+
> back out to an external directory.
301+
285302
> Caution: importing (copy) from a path that is ALREADY inside the user skills
286303
> dir can race with the copy step. When editing an installed skill, edit the
287304
> files in place, then re-import from a separate staging copy — or just verify
@@ -292,12 +309,12 @@ python3 scripts/aionui_api.py post /api/skills/import-symlink '{"skill_path":"/a
292309
For skills that live outside the standard dirs:
293310

294311
```bash
295-
python3 scripts/aionui_api.py post /api/skills/scan '{"path":"/abs/dir"}' # find skills under a dir
312+
python3 scripts/aionui_api.py post /api/skills/scan '{"folder_path":"/abs/dir"}' # find skills under a dir
296313
python3 scripts/aionui_api.py get /api/skills/detect-paths # candidate skill locations
297314
python3 scripts/aionui_api.py get /api/skills/detect-external # external skill dirs
298315
python3 scripts/aionui_api.py get /api/skills/external-paths # list registered external paths
299-
python3 scripts/aionui_api.py post /api/skills/external-paths '{"path":"/abs/dir"}' # add one
300-
python3 scripts/aionui_api.py delete /api/skills/external-paths '{"path":"/abs/dir"}' # remove one
316+
python3 scripts/aionui_api.py post /api/skills/external-paths '{"name":"<label>","path":"/abs/dir"}' # add one (both required)
317+
python3 scripts/aionui_api.py delete /api/skills/external-paths '{"path":"/abs/dir"}' # remove one (path only)
301318
```
302319

303320
The **skills market** is a separate, app-wide toggle:
@@ -308,7 +325,7 @@ The **skills market** is a separate, app-wide toggle:
308325
Put the skill's `name` into the assistant's `enabled_skills`:
309326

310327
```bash
311-
python3 scripts/aionui_api.py put /api/assistants/<id> '{"id":"<id>","enabled_skills":["skill-a","skill-b"]}'
328+
python3 scripts/aionui_api.py put /api/assistants/<id> '{"enabled_skills":["skill-a","skill-b"]}'
312329
```
313330

314331
> `enabled_skills` is the full set — include every skill you want kept, not just
@@ -345,11 +362,13 @@ The `transport` object is one of:
345362
| --- | --- | --- |
346363
| `stdio` | `command`, `args?` (string[]), `env?` (map) | local process servers (npx/uvx/binaries) |
347364
| `sse` | `url`, `headers?` (map) | remote Server-Sent-Events servers (legacy) |
348-
| `http` / `streamable_http` | `url`, `headers?` (map) | remote HTTP servers (Streamable HTTP) |
365+
| `http` | `url`, `headers?` (map) | remote HTTP servers (Streamable HTTP) |
349366

350367
> `headers` is an optional string→string map for auth (e.g. `{"Authorization":
351-
> "Bearer …"}`). `streamable_http` is accepted on create/update but always
352-
> normalizes to `http` in responses — don't expect `streamable_http` echoed back.
368+
> "Bearer …"}`). The REST API accepts exactly these three `type` values —
369+
> `stdio`, `sse`, `http`. Use `http` for Streamable-HTTP servers; there is no
370+
> separate `streamable_http` transport type at this layer (sending it fails
371+
> deserialization).
353372
354373
### Create
355374

@@ -378,14 +397,20 @@ python3 scripts/aionui_api.py post /api/mcp/servers '{
378397
returns the server's tool list (or an error / `needs_auth`). Good to run after
379398
creating a remote server.
380399

381-
### Toggle / update / delete
400+
### Fetch one / toggle / update / delete
382401

383402
```bash
403+
python3 scripts/aionui_api.py get /api/mcp/servers/<id> # one server by id
384404
python3 scripts/aionui_api.py post /api/mcp/servers/<id>/toggle # enable <-> disable
385405
python3 scripts/aionui_api.py put /api/mcp/servers/<id> '{"description":"..."}'
386406
python3 scripts/aionui_api.py delete /api/mcp/servers/<id>
387407
```
388408

409+
> Two more list-level helpers exist: `POST /api/mcp/servers/import`
410+
> (`{"servers":[…]}`, bulk-restore a set at once) and `GET /api/mcp/agent-configs`
411+
> (scans installed Agent CLIs and returns their existing MCP configs — a source
412+
> for one-click import).
413+
389414
> Remote servers may need OAuth: `/api/mcp/oauth/check-status`,
390415
> `/api/mcp/oauth/login`, `/api/mcp/oauth/logout` (all `post`), and
391416
> `GET /api/mcp/oauth/authenticated` which lists the server URLs that already
@@ -440,19 +465,21 @@ which protocol it speaks and what models it has. Use this to fill `platform` and
440465

441466
```bash
442467
python3 scripts/aionui_api.py post /api/providers/detect-protocol '{
443-
"platform": "custom",
444468
"base_url": "https://api.deepseek.com/v1",
445469
"api_key": "sk-..."
446470
}'
447471
# -> {"protocol":"openai","confidence":90,"models":[...]}
448472
```
449473

450-
Optional fields on this body: `timeout` (ms), `preferred_protocol` (try a given
451-
protocol first), and `test_all_keys` (bool — probe every key when `api_key`
452-
holds several).
474+
Required on this body: just `base_url` + `api_key` (no `platform` — the backend
475+
detects it). Optional: `timeout` (ms), `preferred_protocol` (try a given protocol
476+
first — one of `openai`, `anthropic`, `gemini`, `unknown`), and `test_all_keys`
477+
(bool — probe every key when `api_key` holds several).
453478

454-
`fetch-models` (`POST /api/providers/fetch-models`, same body) returns just the
455-
model list for a not-yet-saved endpoint.
479+
`fetch-models` (`POST /api/providers/fetch-models`) returns just the model list
480+
for a not-yet-saved endpoint. Its body differs from detect-protocol: `platform`,
481+
`base_url`, `api_key` are all **required** (plus optional `bedrock_config`,
482+
`try_fix`).
456483

457484
### Test a provider connection
458485

@@ -519,11 +546,13 @@ Two stores, both verified:
519546
- `PUT /api/settings/client` — batch-update that store.
520547

521548
`PUT /api/settings/client` is a **partial merge** — send only the keys you want
522-
to change. Read first, change one key, read back.
549+
to change (a key set to `null` deletes it). Its response carries no data, so
550+
always read the store back to confirm.
523551

524552
```bash
525553
python3 scripts/aionui_api.py get /api/settings/client
526554
python3 scripts/aionui_api.py put /api/settings/client '{"ui.zoomFactor": 1.0}'
555+
python3 scripts/aionui_api.py get /api/settings/client # confirm — PUT returns no body
527556
```
528557

529558
> To set which model a given assistant uses, configure that assistant's
@@ -535,19 +564,20 @@ python3 scripts/aionui_api.py put /api/settings/client '{"ui.zoomFactor": 1.0}'
535564

536565
`GET /api/agents/management` lists the available engines (`aionrs`, `claude`,
537566
`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
567+
the `/management` sub-path. Each row is rich: alongside `id`, `name`, `enabled`
568+
(toggled on), `installed` (spawn command resolvable on `$PATH`), `team_capable`
569+
(can run in a team), `backend`, `agent_type`, and a `status` of `online` /
570+
`offline` / `missing`, it also carries `config_options`, `available_modes`,
571+
`available_models` (when the engine advertises them), plus `last_check_*`
572+
diagnostics. Check `installed` (and `status`) before binding an assistant to that
542573
engine (via its `agent_id` — see *Picking the engine* above).
543574

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.
575+
> What the management row does **not** carry is the rest of the engine
576+
> `handshake``agent_capabilities`, `auth_methods`, `available_commands`. For
577+
> those, `POST /api/agents/refresh` re-scans agents and returns each one's full
578+
> metadata (`available` + `handshake`). The at-a-glance modes/models are already
579+
> on the management row; reach for `refresh` when you need capabilities, auth
580+
> methods, or the command list.
551581
552582
---
553583

@@ -623,8 +653,16 @@ python3 scripts/aionui_api.py get /api/cron/jobs/<id> # one
623653
python3 scripts/aionui_api.py put /api/cron/jobs/<id> '{"enabled": false}' # partial update (pause)
624654
python3 scripts/aionui_api.py post /api/cron/jobs/<id>/run # run it once right now
625655
python3 scripts/aionui_api.py delete /api/cron/jobs/<id> # remove it
656+
python3 scripts/aionui_api.py get /api/cron/jobs/<id>/conversations # conversations this job has spawned
657+
python3 scripts/aionui_api.py get /api/cron/jobs/<id>/skill # {"has_skill": bool}
658+
python3 scripts/aionui_api.py post /api/cron/jobs/<id>/skill '{"content":"<SKILL.md body>"}' # attach/replace a per-job skill
659+
python3 scripts/aionui_api.py delete /api/cron/jobs/<id>/skill # remove the attached skill
626660
```
627661

662+
> A job can carry its own inline **skill** (a `SKILL.md`-style instruction body)
663+
> via `.../skill` — this is the `cron` skill source. Handy when a scheduled task
664+
> needs bespoke instructions that shouldn't live in the shared registry.
665+
628666
`PUT` is a partial update — send only what changes (`name`, `description`,
629667
`enabled`, `schedule`, `message`, `execution_mode`, `agent_config`,
630668
`conversation_title`, `max_retries`). Read the job back to confirm its

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,20 @@ python3 scripts/aion_diag.py messages <id> [--limit N] [--errors] # message hi
104104
```
105105

106106
- `conversation <id>` is the workhorse. It returns the live `runtime` block
107-
(`state`, `task_status`, `is_processing`, `turn_id`), the 5 most recent
108-
**error** messages, and a `stuck_hint` when `state=running` +
109-
`is_processing=true`.
107+
(`state`, `task_status`, `is_processing`, `turn_id`, plus `can_send_message`,
108+
`has_task`, `pending_confirmations`), the 5 most recent **error** messages, and
109+
a `stuck_hint` when `state=running` + `is_processing=true`. Note `state` (the
110+
runtime machine state) and `task_status` are different fields — stuck detection
111+
keys off `state`.
110112
- **Stuck detection is comparative, not absolute.** A single `running` snapshot
111113
is normal — that may just be the active turn. To confirm a hang, run
112114
`conversation <id>` a few times seconds apart: if `turn_id` and runtime never
113115
change while no new messages arrive, it's stuck. Cross-check with
114116
`logs --conv <id>`.
117+
- **Not every non-progressing turn is stuck.** `state=waiting_confirmation` (or
118+
`pending_confirmations > 0`) means the turn is *blocked on a user approval*,
119+
not hung — the fix is to answer the pending confirmation, not to restart the
120+
conversation. The `stuck_hint` distinguishes these two cases.
115121
- `messages <id> --errors` pulls just the failed messages/tool-calls for that
116122
conversation from the unified `messages` table (engine-agnostic).
117123

@@ -122,8 +128,9 @@ python3 scripts/aion_diag.py providers
122128
```
123129

124130
Lists every configured provider with its `model_health` (`status`, `latency`,
125-
`last_check`) and an `unhealthy_models` summary. A provider whose models show
126-
non-`healthy` status, huge `latency`, or a stale `last_check` is the suspect.
131+
`last_check`, and an `error` string on the last failed check) and an
132+
`unhealthy_models` summary. A provider whose models show non-`healthy` status,
133+
huge `latency`, or a stale `last_check` is the suspect.
127134
Then confirm with the log (filter by the provider's base_url or id):
128135

129136
```bash

crates/aionui-app/assets/builtin-skills/aionui-troubleshooting/scripts/aion_diag.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
python3 aion_diag.py providers # LLM providers + model_health (api_key REDACTED)
2121
python3 aion_diag.py mcp # MCP servers + tool counts (flags enabled-but-0-tools)
2222
python3 aion_diag.py teams # teams + members + each member's conversation state
23-
python3 aion_diag.py crons # scheduled jobs from SQLite (no REST API for these)
23+
python3 aion_diag.py crons # scheduled jobs read straight from SQLite (a REST API also exists)
2424
2525
python3 aion_diag.py logs [--lines N] [--errors] [--conv <id>] # tail aioncore log
2626
python3 aion_diag.py overview # one-shot health snapshot across everything
@@ -266,12 +266,18 @@ def cmd_conversation(argv):
266266
})
267267
return
268268
runtime = d.get("runtime", {})
269-
# engine-agnostic stuck-detection hint
269+
# engine-agnostic stuck-detection hint. `state` is the runtime machine state
270+
# (idle/starting/running/cancelling/waiting_confirmation); `task_status` is a
271+
# different field (pending/running/finished), so key off `state` here.
270272
stuck_hint = None
271-
if runtime.get("is_processing") and runtime.get("task_status") == "running":
273+
if runtime.get("is_processing") and runtime.get("state") == "running":
272274
stuck_hint = ("state=running & is_processing=true — if this has not changed "
273275
"across repeated checks, the turn may be stuck. Compare turn_id "
274276
"over time and check recent errors + logs.")
277+
elif runtime.get("state") == "waiting_confirmation" or runtime.get("pending_confirmations"):
278+
stuck_hint = ("state=waiting_confirmation — the turn is blocked awaiting a "
279+
"user approval, not stuck. Resolve the pending confirmation to "
280+
"let it continue (pending_confirmations > 0).")
275281
errs = _rows(
276282
"SELECT msg_id, type, substr(content,1,300) AS content, created_at "
277283
"FROM messages WHERE conversation_id=? AND status='error' "
@@ -373,7 +379,9 @@ def cmd_teams(argv):
373379

374380

375381
def cmd_crons(argv):
376-
# No REST API for crons — read AionUi's SQLite store directly.
382+
# A REST API exists (/api/cron/jobs), but for diagnosis we read AionUi's
383+
# SQLite store directly — it surfaces the run-state columns in one shot and
384+
# needs no auth token.
377385
rows = _rows(
378386
"SELECT id, name, enabled, schedule_kind, schedule_value, "
379387
"schedule_description, last_status, last_error, "

0 commit comments

Comments
 (0)