Skip to content

Commit 79f8e5e

Browse files
committed
chore: prepare release 1.21.6
1 parent db46c48 commit 79f8e5e

20 files changed

Lines changed: 691 additions & 70 deletions

CHANGELOG-zh.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
66
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)
77

8+
## [1.21.6] - 2026-05-30
9+
10+
### 发布概览
11+
- **远程管理与插件安装稳定性补丁** — 收敛远程主机状态持久化、插件安装进度和全局工作区刷新行为。
12+
13+
### 用户可见更新
14+
- **远程主机状态持久化** — 远程管理切换主机或重新进入页面后,会恢复上次检测到的远程环境和 CLI 状态。
15+
- **连接状态语义优化** — 主机卡片改为显示“上次连接成功”,避免误解为 SSH 长连接仍在保持。
16+
- **插件安装进度优化** — Claude 插件安装 Skills 时,成功、部分成功和失败状态都会保留中文进度信息。
17+
- **插件卸载确认修复** — 删除已安装插件 Skills 现在会等待确认弹窗,不会在确认前先删除。
18+
- **全局工作区刷新修复** — Agent 详情页不再因为文件访问或元数据事件触发无意义刷新。
19+
20+
### 开发者与治理更新
21+
- **远程状态迁移** — 远程主机记录新增远程系统、Shell、主目录、CLI 可用性和 CLI 版本持久化字段。
22+
- **连接状态回写** — 远程扫描、安装、删除、更新和详情读取命令都会在成功或失败后刷新主机连接状态。
23+
- **克隆超时处理** — 插件仓库克隆改为按“无进度空闲超时”判断,不再固定 120 秒后失败。
24+
- **回归覆盖** — 新增文件监听事件过滤测试,并保持远程管理、插件市场、迁移和前端构建验证通过。
25+
826
## [1.21.5] - 2026-05-29
927

1028
### 发布概览

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.21.6] - 2026-05-30
9+
10+
### Release Overview
11+
- **Remote Management and Plugin Install Stability Patch** — Tightens remote host state persistence, plugin install progress, and Global Workspace refresh behavior.
12+
13+
### User-facing
14+
- **Remote Host Status Persistence** — Remote Management now restores the last detected host environment and CLI state after switching hosts or returning to the page.
15+
- **Clear Connection Semantics** — Host cards now show "Last connected" instead of implying a live SSH session is still open.
16+
- **Plugin Install Progress** — Claude plugin skill installation now keeps localized progress visible through success, partial success, and failure states.
17+
- **Safer Plugin Uninstall** — Removing installed plugin skills now waits for the confirmation dialog before deleting anything.
18+
- **Global Workspace Refresh Fix** — Agent detail pages no longer refresh from file access or metadata-only watcher events.
19+
20+
### Developer & Governance
21+
- **Remote State Migration** — Added persisted remote OS, shell, home directory, CLI availability, and CLI version fields to host records.
22+
- **Connection Status Updates** — Remote scan, install, delete, update, and detail commands now refresh host connection state on success and failure.
23+
- **Clone Timeout Handling** — Plugin repository cloning now uses an idle no-progress timeout instead of failing after a fixed 120 seconds.
24+
- **Regression Coverage** — Added focused coverage for file watcher event filtering and kept remote management, marketplace, migration, and build checks passing.
25+
826
## [1.21.5] - 2026-05-29
927

1028
### Release Overview

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "skills-manager-plus",
33
"private": true,
4-
"version": "1.21.5",
4+
"version": "1.21.6",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "skills-manager-plus"
3-
version = "1.21.5"
3+
version = "1.21.6"
44
description = "Skills-Manager-Plus - AI Agent Skills Central Manager"
55
authors = ["tianliang"]
66
license = "MIT"

src-tauri/src/commands/marketplace.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,18 +355,33 @@ fn install_plugin_skills_with_progress(
355355
let mut skipped = 0usize;
356356
let mut failed: Vec<String> = Vec::new();
357357

358-
for skill_name in &skill_names {
358+
let total = skill_names.len();
359+
for (index, skill_name) in skill_names.iter().enumerate() {
360+
let progress_prefix = format!("{}/{}|{}", index + 1, total, skill_name);
361+
if let Some(callback) = progress.as_ref() {
362+
callback("installing", &progress_prefix);
363+
}
364+
359365
if existing_names.contains(skill_name) {
360366
skipped += 1;
367+
if let Some(callback) = progress.as_ref() {
368+
callback("install_skipped", &progress_prefix);
369+
}
361370
continue;
362371
}
363372

364373
let Some(source_dir) = find_skill_dir_in_repo(&temp_dir, skill_name) else {
365374
failed.push(skill_name.clone());
375+
if let Some(callback) = progress.as_ref() {
376+
callback("install_failed", &progress_prefix);
377+
}
366378
continue;
367379
};
368380
if !skill_metadata::is_valid_skill_dir(&source_dir) {
369381
failed.push(skill_name.clone());
382+
if let Some(callback) = progress.as_ref() {
383+
callback("install_failed", &progress_prefix);
384+
}
370385
continue;
371386
}
372387

@@ -375,12 +390,18 @@ fn install_plugin_skills_with_progress(
375390
let install_result = if dest_existed {
376391
if !skill_metadata::is_valid_skill_dir(&dest_dir) {
377392
skipped += 1;
393+
if let Some(callback) = progress.as_ref() {
394+
callback("install_skipped", &progress_prefix);
395+
}
378396
continue;
379397
}
380398
match install_result_for_existing_skill_dir(skill_name, &dest_dir) {
381399
Ok(result) => result,
382400
Err(e) => {
383401
failed.push(format!("{}: {}", skill_name, e.message));
402+
if let Some(callback) = progress.as_ref() {
403+
callback("install_failed", &progress_prefix);
404+
}
384405
continue;
385406
}
386407
}
@@ -393,6 +414,9 @@ fn install_plugin_skills_with_progress(
393414
Ok(result) => result,
394415
Err(e) => {
395416
failed.push(format!("{}: {}", skill_name, e));
417+
if let Some(callback) = progress.as_ref() {
418+
callback("install_failed", &progress_prefix);
419+
}
396420
continue;
397421
}
398422
}
@@ -412,6 +436,9 @@ fn install_plugin_skills_with_progress(
412436
let _ = std::fs::remove_dir_all(&dest_dir);
413437
}
414438
failed.push(format!("{}: {}", skill_name, e.message));
439+
if let Some(callback) = progress.as_ref() {
440+
callback("install_failed", &progress_prefix);
441+
}
415442
continue;
416443
}
417444
};
@@ -424,6 +451,9 @@ fn install_plugin_skills_with_progress(
424451
let _ = std::fs::remove_dir_all(&dest_dir);
425452
}
426453
failed.push(format!("{}: {}", skill_name, e.message));
454+
if let Some(callback) = progress.as_ref() {
455+
callback("install_failed", &progress_prefix);
456+
}
427457
continue;
428458
}
429459
};
@@ -438,12 +468,19 @@ fn install_plugin_skills_with_progress(
438468

439469
if let Err(e) = store.insert_plugin_install(&install_rec) {
440470
failed.push(format!("{}: {}", skill_name, e));
471+
if let Some(callback) = progress.as_ref() {
472+
callback("install_failed", &progress_prefix);
473+
}
441474
continue;
442475
}
443476

444477
installed += 1;
445478
}
446479

480+
if let Some(callback) = progress.as_ref() {
481+
callback("done", &format!("{installed}|{skipped}|{}", failed.len()));
482+
}
483+
447484
Ok(BatchPluginInstallResult {
448485
installed,
449486
skipped,

src-tauri/src/commands/remote_workspace.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ use crate::core::{
1919
get_cached_remote_skill_detail, get_remote_host, get_remote_skill_detail,
2020
insert_remote_operation_log, install_center_skill_to_remote, list_remote_agent_paths,
2121
list_remote_hosts, list_remote_operation_logs, list_remote_skill_snapshots,
22-
parse_ssh_config, scan_remote_host, update_remote_host, update_remote_skill_from_center,
23-
CredentialRefUpdate, RemoteAgentPathRecord, RemoteAuthType, RemoteHostInput,
24-
RemoteHostRecord, RemoteOperationLogRecord, RemoteSkillDetail, RemoteSkillSnapshot,
25-
SshConfigHost,
22+
parse_ssh_config, scan_remote_host_with_environment, update_remote_host,
23+
update_remote_host_connection_status, update_remote_host_environment,
24+
update_remote_skill_from_center, CredentialRefUpdate, RemoteAgentPathRecord,
25+
RemoteAuthType, RemoteHostInput, RemoteHostRecord, RemoteOperationLogRecord,
26+
RemoteSkillDetail, RemoteSkillSnapshot, SshConfigHost,
2627
},
2728
skill_store::SkillStore,
2829
};
@@ -252,11 +253,13 @@ pub async fn remote_test_connection(
252253
let started_at = chrono::Utc::now().timestamp_millis();
253254
let result = (|| {
254255
let config = build_connection_config(&store, &id, password, passphrase)?;
255-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
256+
let executor = connect_remote_executor(&store, &id, &config, started_at)?;
256257
executor.test_connection().map_err(AppError::network)
257258
})();
258259
match result {
259260
Ok(environment) => {
261+
update_remote_host_environment(&store, &id, started_at, &environment)
262+
.map_err(AppError::db)?;
260263
let summary_json = serde_json::to_string(&serde_json::json!({
261264
"os": &environment.os,
262265
"shell": &environment.shell,
@@ -283,6 +286,8 @@ pub async fn remote_test_connection(
283286
Err(err) => {
284287
let error_code = error_kind_code(&err.kind).to_string();
285288
let error_message = err.message.clone();
289+
let _ =
290+
update_remote_host_connection_status(&store, &id, None, Some(&error_message));
286291
let _ = insert_remote_operation_log(
287292
&store,
288293
&remote_operation_log(
@@ -314,11 +319,13 @@ pub async fn remote_install_cli(
314319
let started_at = chrono::Utc::now().timestamp_millis();
315320
let result = (|| {
316321
let config = build_connection_config(&store, &id, password, passphrase)?;
317-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
322+
let executor = connect_remote_executor(&store, &id, &config, started_at)?;
318323
install_remote_cli(&executor)
319324
})();
320325
match result {
321326
Ok(report) => {
327+
update_remote_host_environment(&store, &id, started_at, &report.environment)
328+
.map_err(AppError::db)?;
322329
let summary_json = serde_json::to_string(&serde_json::json!({
323330
"asset_name": &report.asset_name,
324331
"install_path": &report.install_path,
@@ -345,6 +352,8 @@ pub async fn remote_install_cli(
345352
Err(err) => {
346353
let error_code = error_kind_code(&err.kind).to_string();
347354
let error_message = err.message.clone();
355+
let _ =
356+
update_remote_host_connection_status(&store, &id, None, Some(&error_message));
348357
let _ = insert_remote_operation_log(
349358
&store,
350359
&remote_operation_log(
@@ -379,13 +388,18 @@ pub async fn remote_scan_host(
379388
.map_err(AppError::db)?
380389
.ok_or_else(|| AppError::not_found("Remote host not found"))?;
381390
let config = build_connection_config_for_host(&store, &host, password, passphrase)?;
382-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
383-
scan_remote_host(&store, &executor, &host, &config).map_err(AppError::network)
391+
let executor = connect_remote_executor(&store, &id, &config, started_at)?;
392+
scan_remote_host_with_environment(&store, &executor, &host, &config)
393+
.map_err(AppError::network)
384394
})();
385395
match result {
386-
Ok(snapshots) => {
396+
Ok((snapshots, environment)) => {
397+
update_remote_host_environment(&store, &id, started_at, &environment)
398+
.map_err(AppError::db)?;
387399
let summary_json = serde_json::to_string(&serde_json::json!({
388400
"snapshot_count": snapshots.len(),
401+
"cli_available": environment.cli_available,
402+
"cli_version": environment.cli_version,
389403
}))
390404
.ok();
391405
insert_remote_operation_log(
@@ -406,6 +420,8 @@ pub async fn remote_scan_host(
406420
Err(err) => {
407421
let error_code = error_kind_code(&err.kind).to_string();
408422
let error_message = err.message.clone();
423+
let _ =
424+
update_remote_host_connection_status(&store, &id, None, Some(&error_message));
409425
let _ = insert_remote_operation_log(
410426
&store,
411427
&remote_operation_log(
@@ -436,11 +452,12 @@ pub async fn remote_delete_skill(
436452
) -> Result<RemoteOperationLogRecord, AppError> {
437453
let store = store.inner().clone();
438454
tauri::async_runtime::spawn_blocking(move || {
455+
let started_at = chrono::Utc::now().timestamp_millis();
439456
let host = get_remote_host(&store, &host_id)
440457
.map_err(AppError::db)?
441458
.ok_or_else(|| AppError::not_found("Remote host not found"))?;
442459
let config = build_connection_config_for_host(&store, &host, password, passphrase)?;
443-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
460+
let executor = connect_remote_executor(&store, &host_id, &config, started_at)?;
444461
delete_remote_skill(&store, &executor, &host_id, &agent_id, &skill_name)
445462
.map_err(AppError::network)
446463
})
@@ -458,11 +475,12 @@ pub async fn remote_install_center_skill(
458475
) -> Result<RemoteOperationLogRecord, AppError> {
459476
let store = store.inner().clone();
460477
tauri::async_runtime::spawn_blocking(move || {
478+
let started_at = chrono::Utc::now().timestamp_millis();
461479
let host = get_remote_host(&store, &host_id)
462480
.map_err(AppError::db)?
463481
.ok_or_else(|| AppError::not_found("Remote host not found"))?;
464482
let config = build_connection_config_for_host(&store, &host, password, passphrase)?;
465-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
483+
let executor = connect_remote_executor(&store, &host_id, &config, started_at)?;
466484
install_center_skill_to_remote(&store, &executor, &host_id, &agent_id, &skill_id)
467485
.map_err(AppError::network)
468486
})
@@ -480,11 +498,12 @@ pub async fn remote_update_skill(
480498
) -> Result<RemoteOperationLogRecord, AppError> {
481499
let store = store.inner().clone();
482500
tauri::async_runtime::spawn_blocking(move || {
501+
let started_at = chrono::Utc::now().timestamp_millis();
483502
let host = get_remote_host(&store, &host_id)
484503
.map_err(AppError::db)?
485504
.ok_or_else(|| AppError::not_found("Remote host not found"))?;
486505
let config = build_connection_config_for_host(&store, &host, password, passphrase)?;
487-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
506+
let executor = connect_remote_executor(&store, &host_id, &config, started_at)?;
488507
update_remote_skill_from_center(&store, &executor, &host_id, &agent_id, &skill_name)
489508
.map_err(AppError::network)
490509
})
@@ -513,7 +532,8 @@ pub async fn remote_get_skill_detail(
513532
.map_err(AppError::db)?
514533
.ok_or_else(|| AppError::not_found("Remote host not found"))?;
515534
let config = build_connection_config_for_host(&store, &host, password, passphrase)?;
516-
let executor = SshSftpExecutor::connect(&config).map_err(AppError::network)?;
535+
let started_at = chrono::Utc::now().timestamp_millis();
536+
let executor = connect_remote_executor(&store, &host_id, &config, started_at)?;
517537
get_remote_skill_detail(&store, &executor, &host_id, &agent_id, &skill_name)
518538
.map_err(AppError::network)
519539
})
@@ -721,6 +741,31 @@ fn build_connection_config_for_host(
721741
})
722742
}
723743

744+
fn connect_remote_executor(
745+
store: &SkillStore,
746+
host_id: &str,
747+
config: &RemoteConnectionConfig,
748+
connected_at: i64,
749+
) -> Result<SshSftpExecutor, AppError> {
750+
match SshSftpExecutor::connect(config) {
751+
Ok(executor) => {
752+
update_remote_host_connection_status(store, host_id, Some(connected_at), None)
753+
.map_err(AppError::db)?;
754+
Ok(executor)
755+
}
756+
Err(error) => {
757+
let app_error = AppError::network(error);
758+
let _ = update_remote_host_connection_status(
759+
store,
760+
host_id,
761+
None,
762+
Some(&app_error.message),
763+
);
764+
Err(app_error)
765+
}
766+
}
767+
}
768+
724769
fn resolve_secret(
725770
store: &SkillStore,
726771
provided_secret: Option<String>,

0 commit comments

Comments
 (0)