Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
66b487a
feat(agent): add connection testing and bare assistant projection
Jun 15, 2026
3c742a9
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 15, 2026
9ced726
chore(assistant): remove unused preset id whitelist asset
Jun 16, 2026
645eff8
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 16, 2026
5f10d94
feat(assistant): prioritize bare assistants on first bootstrap
Jun 16, 2026
8431999
merge: bring origin/main into feat/agent-connection-testing-phase2
Jun 16, 2026
6523326
test(assistant): cover bare assistant projection
Jun 16, 2026
6a040d9
feat(channel): add backend-owned channel settings API
Jun 16, 2026
ab35b57
chore: apply auto-fixes (fmt + clippy)
Jun 16, 2026
24fb0ce
test(api): refresh assistant response fixture
Jun 16, 2026
6226104
feat(channel): resolve bindings from assistants
Jun 16, 2026
c3e02ce
fix(agent): kill probe process group to stop wrapper grandchild leak
Jun 16, 2026
d821d4d
feat(team): persist assistant identity across team flows
Jun 16, 2026
c176bb8
refactor(cron): persist assistant identity in cron config
Jun 16, 2026
940e287
feat(agent): probe managed builtin acp health
Jun 16, 2026
f14083f
refactor(agent): drop legacy backend health check route
Jun 16, 2026
4bdab9b
refactor(cron): create conversations with assistant identity
Jun 16, 2026
7263515
refactor(channel): normalize assistant bindings on write
Jun 16, 2026
987f8f1
feat(agent): surface management diagnostics guidance
Jun 16, 2026
742ba2b
fix(assistant): forbid editing generated assistants
Jun 16, 2026
f2e7975
refactor(team): persist assistant identity for new agents
Jun 16, 2026
86033c2
refactor(conversation): inject assistant runtime seeds
Jun 16, 2026
907ace6
refactor(cron): strip legacy agent ids on assistant writes
Jun 16, 2026
d6c8161
refactor(team): prefer assistant ids in mcp tooling
Jun 16, 2026
afb071e
fix(team): resolve spawn backend from assistant ids
Jun 16, 2026
6709f85
refactor(team): derive team backends from assistants
Jun 16, 2026
712d5fc
refactor(conversation): drop redundant preset extra writes
Jun 16, 2026
dfd6d18
refactor(team): prefer assistant ids in leader prompts
Jun 16, 2026
f71aa96
refactor(channel): create conversations through assistant identities
Jun 16, 2026
f5ebc0a
refactor(team): seed lead prompts from assistants
Jun 16, 2026
16d53ed
refactor(team): reword mcp tools around assistants
Jun 16, 2026
fe74d85
refactor(team): reword prompts around assistants
Jun 16, 2026
ab711d4
refactor(channel): split assistant setting read and write contracts
Jun 17, 2026
c673afa
refactor(channel): prefer assistant-first channel settings
Jun 17, 2026
a885ef2
test(channel): expect assistant-only binding writes
Jun 17, 2026
5568f5d
refactor(team): make backend optional for assistant-led requests
Jun 17, 2026
93c7371
refactor(channel): drop direct agent switching
Jun 17, 2026
258b139
refactor(team): make prompts and tools assistant-first
Jun 17, 2026
940f199
refactor(team): remove preset assistant wording
Jun 17, 2026
b041484
refactor(team): stop writing preset assistant ids
Jun 17, 2026
af4500a
refactor(team): stop echoing legacy custom agent ids
Jun 17, 2026
5301e62
refactor(cron): canonicalize legacy assistant ids on write
Jun 17, 2026
46bf6f1
refactor(cron): stop promoting legacy assistant ids
Jun 17, 2026
fb0a657
refactor(cron): split cron write agent config dto
Jun 17, 2026
d3e96eb
refactor(conversation): expose explicit assistant identity
Jun 17, 2026
e65ba82
test(cron): stop asserting legacy preset assistant ids
Jun 17, 2026
da98c48
refactor(cron): derive runtime type from assistants
Jun 17, 2026
5b3d125
refactor(team): pass assistant identity to conversations
Jun 17, 2026
fe91dd8
refactor(team): accept assistant-first request payloads
Jun 17, 2026
2f5ee95
refactor(team): drop legacy mcp spawn aliases
Jun 17, 2026
513a408
refactor(team): canonicalize legacy custom agent ids
Jun 17, 2026
e011837
refactor(conversation): derive create type from assistants
Jun 17, 2026
4f761ac
refactor(channel): stop persisting assistant backends in extra
Jun 17, 2026
3fd2bbf
refactor(conversation): stop writing legacy assistant ids
Jun 17, 2026
d6ca58a
refactor(cron): omit legacy agent hints for assistant creates
Jun 17, 2026
03a7f5f
refactor(team): derive guide leaders from assistant conversations
Jun 17, 2026
0691319
refactor(team): require assistant ids for mcp spawning
Jun 17, 2026
d6dd73f
refactor(team): emit assistant-native response fields
Jun 17, 2026
6e10e04
refactor(channel): normalize assistant-first platform settings
Jun 17, 2026
bae5468
refactor(conversation): prefer persisted acp session identity
Jun 17, 2026
1e35339
refactor(channel): prefer assistant names for conversations
Jun 17, 2026
6b01b29
fix(conversation): persist resolved agent identity from snapshot
Jun 17, 2026
826a96e
refactor(team): prefer assistant ids in command shims
Jun 17, 2026
224b6fb
fix(conversation): prefer snapshot runtime identity
Jun 17, 2026
1efb084
refactor(cron): strip legacy ids from assistant responses
Jun 17, 2026
764a64b
refactor(cron): prefer runtime backend over stale extra
Jun 17, 2026
466a938
feat(agent): expose backend logo catalog endpoint
Jun 17, 2026
05aea56
fix(cron): prefer assistant backend when creating jobs
Jun 17, 2026
383036e
refactor(cron): canonicalize legacy assistant ids in responses
Jun 17, 2026
9a9985e
refactor(channel): clear sessions on settings updates
Jun 17, 2026
6de2169
refactor(team): query models by assistant identity
Jun 17, 2026
93d8ca5
refactor(cron): reject legacy write agent fields
Jun 17, 2026
879f439
refactor(cron): strip legacy backend from assistant responses
Jun 17, 2026
bf647f6
refactor(team): route stdio model lookup by assistant
Jun 17, 2026
7107f19
refactor(team): rename list models response to backends
Jun 17, 2026
95e2278
refactor(cron): derive backend from assistants
Jun 17, 2026
d1df342
refactor(team): ignore caller backend for assistants
Jun 17, 2026
9ce4833
refactor(channel): canonicalize legacy backend bindings
Jun 17, 2026
273968d
refactor(cron): require assistant ids for public create requests
Jun 17, 2026
6144f3f
refactor(team): reject legacy custom agent ids in write dtos
Jun 17, 2026
98b718a
refactor(team): require assistant ids for public writes
Jun 18, 2026
0e7fcc4
refactor(agent): drop legacy public agents catalog
Jun 18, 2026
ca65c80
refactor(team): prefer assistant avatars in responses
Jun 18, 2026
373d2fc
fix(agent): reap probe process tree to stop orphan leak
Jun 18, 2026
171ca04
fix(agent): key logo catalog by agent_type when backend is null
Jun 18, 2026
8c61784
refactor(team): reject legacy stdio command aliases
Jun 18, 2026
aeb842b
refactor(cron): require assistant backend resolution on update
Jun 18, 2026
0ed5e36
refactor(team): prefer assistant-first response fields
Jun 18, 2026
49ef39f
fix(team): append gemini to guide backends response
Jun 18, 2026
cc2d3f8
refactor(cron): prefer assistant ids in task extras
Jun 18, 2026
e72d955
refactor(team): reject legacy guide assistant aliases
Jun 18, 2026
cffae6d
test(team): align app e2e with assistant responses
Jun 18, 2026
5250f35
refactor(team): omit agent types for assistant conversations
Jun 18, 2026
c9f5228
refactor(channel): reject legacy assistant write fields
Jun 18, 2026
2eb2552
refactor(team): reject backend in public write dto
Jun 18, 2026
5a9ec23
refactor(cron): split read and write agent config dto
Jun 18, 2026
40f2a31
refactor(channel): reject unresolved assistant bindings
Jun 18, 2026
79e95ec
feat(agent): add command/env override columns to agent_metadata
Jun 18, 2026
d037fb1
feat(agent): repo read/write for command and env overrides
Jun 18, 2026
3a15f1f
fix(conversation): prefer assistant runtime seeds on create
Jun 18, 2026
f5d049e
feat(agent): add env override key blocklist
Jun 18, 2026
92cc22d
feat(agent): merge command/env overrides at row projection
Jun 18, 2026
80def58
feat(agent): expose override summary fields on management row
Jun 18, 2026
f8b10bb
feat(agent): add overrides endpoint and service
Jun 18, 2026
5e10d8e
fix(team): accept guide JSON success payloads
Jun 18, 2026
6039804
fix(team): accept guide JSON success payloads
Jun 18, 2026
2970ff6
feat(agent): distinguish needs_auth from unavailable status
Jun 18, 2026
e96e18e
merge: resolve probe cleanup and team guide updates
Jun 18, 2026
0fa143c
refactor(team): finish assistant-first guide prompts
Jun 18, 2026
b2edf58
refactor(team): sync solo guide prompt with assistant-first copy
Jun 18, 2026
e11141b
fix(agent): implement update_agent_overrides in test stubs
Jun 18, 2026
48eecf0
fix(team): add override fields to AgentMetadataRow test constructors
Jun 18, 2026
1c22001
refactor(channel): default to bare assistant bindings
Jun 18, 2026
0260b83
fix(team): close assistant-first guide handoff
Jun 18, 2026
2737553
fix(agent): stop leaking override env on management list
Jun 18, 2026
7e7dc4d
fix(agent): clear needs_auth on session success
Jun 18, 2026
ea7893b
Merge branch 'feat/agent-self-repair' into feat/agent-connection-test…
Jun 18, 2026
c4072a9
fix(team): structure assistant-first errors for i18n
Jun 18, 2026
3ed793f
fix(assistant): fall back to agent_type for empty bare backend
Jun 18, 2026
86e8d70
fix(cron): resolve assistant backend from snapshots
Jun 22, 2026
019fc66
refactor(agent): replace available/unavailable/needs_auth with online…
Jun 22, 2026
d279371
feat(agent): probe session/new to detect auth and gate aionrs on prov…
Jun 22, 2026
a4f437e
chore(merge): merge origin main into agent connection branch
Jun 22, 2026
9f4d84a
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 22, 2026
8f170cc
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 22, 2026
5456a25
Merge branch 'feat/agent-connection-testing-phase2' of github.com:iOf…
Jun 22, 2026
82f848e
fix(team): bootstrap TeamRun for assistant-first creation
Jun 22, 2026
5f05288
fix(assistant): resolve aionrs agent status by agent_type, not just b…
Jun 22, 2026
65159ae
chore: apply auto-fixes (fmt + clippy)
Jun 22, 2026
0eefd92
fix(ci): stabilize agent availability checks
Jun 22, 2026
5380220
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 23, 2026
cee9c09
fix(assistant): unify assistant agent id storage
Jun 23, 2026
c74e32b
perf(agent): remove background availability probes
Jun 23, 2026
237eb92
refactor(assistant): normalize assistant and cron identity
Jun 23, 2026
3cb4334
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 23, 2026
1b737a2
chore: apply auto-fixes (fmt + clippy)
Jun 23, 2026
607a409
test(cron): align workspace e2e fixtures with assistant config
Jun 23, 2026
643be6b
fix(assistant): include agent id in response projections
Jun 23, 2026
f50eb38
chore: apply auto-fixes (fmt + clippy)
Jun 23, 2026
6af8c1d
chore: apply auto-fixes (fmt + clippy)
Jun 24, 2026
3115845
refactor(assistant): expose acp backend explicitly
Jun 24, 2026
2a13562
refactor(assistant): remove duplicate agent id from response
Jun 24, 2026
69184e1
chore: apply auto-fixes (fmt + clippy)
Jun 24, 2026
c637666
Merge remote-tracking branch 'origin/main' into feat/agent-connection…
Jun 24, 2026
225db7b
feat: unify skill catalog database source
Jun 25, 2026
a2e5111
test: allow cron skills in unified catalog e2e
Jun 25, 2026
a8055cd
Merge remote-tracking branch 'origin/main' into fix/skill-management-db
Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 36 additions & 7 deletions crates/aionui-ai-agent/src/capability/skill_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};

use aionui_db::ISkillRepository;
use regex::Regex;
use tokio::sync::RwLock;
use tracing::{debug, warn};
Expand Down Expand Up @@ -52,6 +53,9 @@ pub struct AcpSkillManager {
/// Consumed by `discover_skills` / `get_skill` (Task 4 / 5 of the refactor).
#[allow(dead_code)]
paths: Arc<aionui_extension::SkillPaths>,
/// User skill state source. When absent, discovery falls back to legacy
/// path-based listing for unit tests that do not stand up a database.
skill_repo: Option<Arc<dyn ISkillRepository>>,
}

impl AcpSkillManager {
Expand All @@ -60,6 +64,16 @@ impl AcpSkillManager {
cache: RwLock::new(HashMap::new()),
discovered: RwLock::new(false),
paths,
skill_repo: None,
})
}

pub fn new_with_repo(paths: Arc<aionui_extension::SkillPaths>, skill_repo: Arc<dyn ISkillRepository>) -> Arc<Self> {
Arc::new(Self {
cache: RwLock::new(HashMap::new()),
discovered: RwLock::new(false),
paths,
skill_repo: Some(skill_repo),
})
}

Expand All @@ -68,16 +82,17 @@ impl AcpSkillManager {
/// Filtering rules:
/// - Auto-inject builtin skills (under `auto-inject/` in the corpus) are
/// always included unless listed in `exclude_builtin_skills`.
/// - Opt-in builtin skills (siblings of `auto-inject/`) and custom/extension
/// skills are included only if `enabled_skills` contains their name.
/// - Opt-in builtin skills (siblings of `auto-inject/`) and custom/cron/
/// extension skills are included only if `enabled_skills` contains their
/// name.
///
/// Populates the cache; subsequent `get_skill(name)` calls read body lazily.
pub async fn discover_skills(
&self,
enabled_skills: Option<&[String]>,
exclude_builtin_skills: Option<&[String]>,
) -> Vec<SkillIndex> {
let items = match aionui_extension::list_available_skills(&self.paths).await {
let items = match self.list_available_skills().await {
Ok(v) => v,
Err(e) => {
warn!(error = %e, "Failed to list skills via extension service");
Expand All @@ -102,7 +117,9 @@ impl AcpSkillManager {
enabled_skills.is_some_and(|en| en.iter().any(|n| n == &item.name))
}
}
aionui_extension::SkillSource::Custom | aionui_extension::SkillSource::Extension => {
aionui_extension::SkillSource::Custom
| aionui_extension::SkillSource::Cron
| aionui_extension::SkillSource::Extension => {
enabled_skills.is_some_and(|en| en.iter().any(|n| n == &item.name))
}
};
Expand Down Expand Up @@ -150,7 +167,7 @@ impl AcpSkillManager {
*discovered = true;
return Vec::new();
}
let items = match aionui_extension::list_available_skills(&self.paths).await {
let items = match self.list_available_skills().await {
Ok(v) => v,
Err(e) => {
warn!(error = %e, "discover_by_names: list_available_skills failed");
Expand Down Expand Up @@ -188,6 +205,16 @@ impl AcpSkillManager {
.collect()
}

async fn list_available_skills(
&self,
) -> Result<Vec<aionui_extension::SkillListItem>, aionui_extension::ExtensionError> {
if let Some(repo) = &self.skill_repo {
aionui_extension::list_available_skills_with_repo(&self.paths, repo.as_ref()).await
} else {
aionui_extension::list_available_skills(&self.paths).await
}
}

/// Return the current skill index without re-scanning.
pub async fn get_skills_index(&self) -> Vec<SkillIndex> {
let cache = self.cache.read().await;
Expand All @@ -205,7 +232,7 @@ impl AcpSkillManager {
/// Returns `None` if the skill is unknown. On first access the body is
/// read via the appropriate channel based on `source`:
/// - `Builtin` → `aionui_extension::read_builtin_skill(&paths, relative)`
/// - `Custom` / `Extension` → direct `tokio::fs::read_to_string(location/SKILL.md)`
/// - `Custom` / `Cron` / `Extension` → direct `tokio::fs::read_to_string(location/SKILL.md)`
pub async fn get_skill(&self, name: &str) -> Option<SkillDefinition> {
// Fast path: check if body is already cached
{
Expand Down Expand Up @@ -239,7 +266,9 @@ impl AcpSkillManager {
String::new()
}
}
aionui_extension::SkillSource::Custom | aionui_extension::SkillSource::Extension => {
aionui_extension::SkillSource::Custom
| aionui_extension::SkillSource::Cron
| aionui_extension::SkillSource::Extension => {
// `location` for scanned user skills is the directory; append SKILL.md.
let skill_file = if def.location.is_dir() {
def.location.join("SKILL.md")
Expand Down
9 changes: 5 additions & 4 deletions crates/aionui-api-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,13 @@ pub use shell::{
SpeechToTextProvider, SpeechToTextResult, SttStreamClientMessage, SttStreamServerMessage, ToolType,
};
pub use skill::{
AddExternalPathRequest, BuiltinAutoSkillResponse, DeleteSkillRequest, ExportSkillRequest,
ExternalSkillSourceResponse, ImportSkillRequest, ImportSkillResponse, MaterializeSkillsRequest,
AddExternalPathRequest, DeleteSkillRequest, ExportSkillRequest, ExternalSkillSourceResponse,
ImportSkillFailureResponse, ImportSkillRequest, ImportSkillResponse, MaterializeSkillsRequest,
MaterializeSkillsResponse, MaterializedSkillRef, NamedPathResponse, ReadAssistantRuleRequest,
ReadBuiltinResourceRequest, ReadSkillInfoRequest, ReadSkillInfoResponse, RemoveExternalPathRequest,
ScanForSkillsRequest, ScanForSkillsResponse, ScannedSkillResponse, SkillListItemResponse, SkillPathsResponse,
SkillSourceResponse, WriteAssistantRuleRequest,
ScanForSkillsRequest, ScanForSkillsResponse, ScannedSkillResponse, SkillImportLimitsResponse,
SkillImportRecordResponse, SkillListItemResponse, SkillPathsResponse, SkillSourceResponse,
WriteAssistantRuleRequest,
};
pub use system::{
ClientPreferencesResponse, SystemSettingsResponse, UpdateClientPreferencesRequest, UpdateSettingsRequest,
Expand Down
92 changes: 80 additions & 12 deletions crates/aionui-api-types/src/skill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
// A. Skill list & info
// ---------------------------------------------------------------------------

/// Origin of a listed skill — `builtin`, `custom`, or `extension`.
/// Origin of a listed skill — `builtin`, `custom`, `cron`, or `extension`.
///
/// Matches the renderer contract in
/// `src/common/adapter/ipcBridge.ts::listAvailableSkills`.
Expand All @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize};
pub enum SkillSourceResponse {
Builtin,
Custom,
Cron,
Extension,
}

Expand All @@ -35,17 +36,6 @@ pub struct SkillListItemResponse {
pub source: SkillSourceResponse,
}

/// An auto-injected built-in skill (`GET /api/skills/builtin-auto`).
///
/// `location` is the relative path the frontend passes back into
/// `POST /api/skills/builtin-skill` (e.g. `"auto-inject/cron/SKILL.md"`).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BuiltinAutoSkillResponse {
pub name: String,
pub description: String,
pub location: String,
}

/// Request body for `POST /api/skills/info`.
#[derive(Debug, Clone, Deserialize)]
pub struct ReadSkillInfoRequest {
Expand Down Expand Up @@ -75,6 +65,54 @@ pub struct ImportSkillResponse {
pub skill_name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub skill_names: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub failed: Vec<ImportSkillFailureResponse>,
}

/// Per-skill failure summary for batch skill import operations.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ImportSkillFailureResponse {
pub source_name: String,
pub code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actual_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub column: Option<i64>,
}

/// One row in the skill import history.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SkillImportRecordResponse {
pub id: String,
pub operation_id: String,
pub source_label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>,
pub source_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_name: Option<String>,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actual_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub column: Option<i64>,
pub created_at: i64,
}

/// Request body for `POST /api/skills/export-symlink`.
Expand Down Expand Up @@ -143,6 +181,13 @@ pub struct SkillPathsResponse {
pub builtin_skills_dir: String,
}

/// Response for `GET /api/skills/import-limits`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SkillImportLimitsResponse {
pub max_file_bytes: u64,
pub max_total_bytes: u64,
}

// ---------------------------------------------------------------------------
// D. Assistant rules & skills
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -366,6 +411,10 @@ mod tests {
serde_json::to_value(SkillSourceResponse::Custom).unwrap(),
serde_json::json!("custom")
);
assert_eq!(
serde_json::to_value(SkillSourceResponse::Cron).unwrap(),
serde_json::json!("cron")
);
assert_eq!(
serde_json::to_value(SkillSourceResponse::Extension).unwrap(),
serde_json::json!("extension")
Expand Down Expand Up @@ -408,10 +457,29 @@ mod tests {
let resp = ImportSkillResponse {
skill_name: "imported-skill".into(),
skill_names: vec!["imported-skill".into(), "second-skill".into()],
failed: vec![ImportSkillFailureResponse {
source_name: "bad-skill".into(),
code: "SKILL_IMPORT_FILE_TOO_LARGE".into(),
error_path: Some("assets/movie.mp4".into()),
actual_bytes: Some(20),
limit_bytes: Some(10),
line: None,
column: None,
}],
};
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["skill_name"], "imported-skill");
assert_eq!(json["skill_names"], json!(["imported-skill", "second-skill"]));
assert_eq!(
json["failed"],
json!([{
"source_name": "bad-skill",
"code": "SKILL_IMPORT_FILE_TOO_LARGE",
"error_path": "assets/movie.mp4",
"actual_bytes": 20,
"limit_bytes": 10
}])
);
assert!(json.get("skillName").is_none());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ python3 scripts/aionui_api.py get /api/skills/paths
### Import a skill into the registry

`POST /api/skills/import` copies a skill folder into the user skills dir and
registers it. `import-symlink` links it instead (good for skills you keep editing
in an external repo).
registers it. It also accepts a parent folder containing multiple skills or a
zip package.

```bash
python3 scripts/aionui_api.py post /api/skills/import '{"skill_path":"/abs/path/to/skill-folder"}'
Expand Down
5 changes: 5 additions & 0 deletions crates/aionui-app/src/router/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use aionui_auth::{
use aionui_channel::channel_routes;
#[cfg(feature = "weixin")]
use aionui_channel::weixin_login_route;
use aionui_common::ApiErrorLogContext;
use aionui_conversation::{conversation_ops_routes, conversation_routes};
use aionui_cron::cron_routes;
use aionui_extension::{extension_routes, hub_routes, skill_routes};
Expand Down Expand Up @@ -312,6 +313,10 @@ async fn normalize_boundary_error_response(request: Request, next: Next) -> Resp

let original_headers = response.headers().clone();
let mut normalized = (status, Json(ErrorResponse::new(error, code))).into_response();
normalized.extensions_mut().insert(ApiErrorLogContext {
code,
message: error.to_owned(),
});
for (name, value) in original_headers.iter() {
if *name != header::CONTENT_TYPE && *name != header::CONTENT_LENGTH {
normalized.headers_mut().insert(name, value.clone());
Expand Down
12 changes: 4 additions & 8 deletions crates/aionui-app/src/router/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ pub async fn build_channel_state(
);
let skill_resolver = Arc::new(aionui_conversation::skill_resolver::ExtensionSkillResolver::new(
services.skill_paths.clone(),
services.skill_repo.clone(),
));
let agent_metadata_repo: Arc<dyn aionui_db::IAgentMetadataRepository> = Arc::new(
aionui_db::SqliteAgentMetadataRepository::new(services.database.pool().clone()),
Expand Down Expand Up @@ -639,6 +640,7 @@ pub fn build_cron_state(services: &AppServices) -> CronRouterState {
let acp_session_repo: Arc<dyn IAcpSessionRepository> = Arc::new(SqliteAcpSessionRepository::new(pool));
let skill_resolver = Arc::new(aionui_conversation::skill_resolver::ExtensionSkillResolver::new(
services.skill_paths.clone(),
services.skill_repo.clone(),
));
let conv_service = ConversationService::new(
services.work_dir.clone(),
Expand Down Expand Up @@ -766,13 +768,6 @@ pub async fn build_extension_states(
let index_manager = HubIndexManager::new(hub_dir, registry.clone());
let installer = HubInstaller::new(index_manager.clone(), registry.clone());

let app_resource_dir = std::env::current_exe()
.ok()
.and_then(|p| p.canonicalize().ok())
.and_then(|p| p.parent().map(|pp| pp.to_path_buf()))
.unwrap_or_else(|| std::path::PathBuf::from("."));
let skill_paths = aionui_extension::resolve_skill_paths(&app_resource_dir, &skill_data_dir);

let ext_paths_mgr = Arc::new(ExternalPathsManager::new(&skill_data_dir).await);

let ext_state = ExtensionRouterState {
Expand All @@ -785,7 +780,8 @@ pub async fn build_extension_states(
};

let skill_state = SkillRouterState {
skill_paths,
skill_paths: services.skill_paths.as_ref().clone(),
skill_repo: services.skill_repo.clone(),
external_paths_manager: ext_paths_mgr,
assistant_dispatcher: None,
};
Expand Down
Loading
Loading