Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Kyrozen は:
| `/api_key` | API キーを変更 |
| `/learn` | プロジェクトファイルを即座にメモリにスキャン |
| `/forget` | 最近の学習を表示;`/forget キーワード` で誤った学習を削除 |
| `/update` | 固定された GitHub `v2.0.3` リリース wheel を再インストール(プロジェクトへ git pull しない) |
| `/update` | 固定された GitHub `v2.0.3` wheel でインストール済みツールを更新;成功後に Kyrozen を再起動(プロジェクトへ git pull しない) |
| `/self-learning` | 個別の自己学習機能をオン/オフ |

### Web UI モード
Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Kyrozen의 동작:
| `/api_key` | API 키 변경 |
| `/learn` | 프로젝트 파일을 즉시 메모리에 스캔 |
| `/forget` | 최근 학습 확인; `/forget 키워드`로 잘못된 학습 삭제 |
| `/update` | 고정된 GitHub `v2.0.3` 릴리스 wheel을 다시 설치 (프로젝트에 git pull을 실행하지 않음) |
| `/update` | 고정된 GitHub `v2.0.3` wheel로 설치된 도구를 제자리에서 업데이트; 성공 후 Kyrozen 재시작 (프로젝트에 git pull을 실행하지 않음) |
| `/self-learning` | 개별 자기 학습 기능 켜기/끄기 |

### Web UI 모드
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Kyrozen will:
| `/api_key` | Change your API key |
| `/learn` | Immediately scan project files into memory |
| `/forget` | Show recent learnings; `/forget keyword` to delete bad learnings |
| `/update` | Reinstall the pinned `v2.0.3` GitHub release wheel (never pulls into a project) |
| `/update` | Update the installed tool in place from the pinned `v2.0.3` GitHub wheel; restart Kyrozen after success (never pulls into a project) |
| `/agent auto\|coder\|researcher` | Choose automatic routing or an isolated learning profile |
| `/learning status [profile]` | Show candidate, canary, active, retired, and rolled-back artifacts |
| `/learning metrics [profile]` | Show verified completion, corrections, errors, cost, and latency metrics |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Kyrozen 会:
| `/api_key` | 更改 API 密钥 |
| `/learn` | 立即扫描项目文件存入记忆 |
| `/forget` | 查看最近的学习记录;`/forget 关键词` 删除错误学习 |
| `/update` | 重新安装固定的 GitHub `v2.0.3` 发布 wheel(不会向项目执行 git pull) |
| `/update` | 使用固定的 GitHub `v2.0.3` wheel 就地更新已安装工具;成功后重启 Kyrozen(不会向项目执行 git pull) |
| `/self-learning` | 开关各项自学习功能 |

### Web UI 模式
Expand Down
2 changes: 1 addition & 1 deletion docs/self-evolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Historical verification snapshot: `51be33361422e55e1f2f00c33a0e0f8c56132a91`
(the post-#54 `main` revision, captured before this #55 documentation-only
update). Snapshot date: 2026-09-04.

Current repository test count at this snapshot: **201 unittest cases**.
Current repository test count at this snapshot: **202 unittest cases**.

## Verified surface

Expand Down
22 changes: 15 additions & 7 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,8 @@ def _load_project_files_into_memory(*, force: bool = False) -> None:

def _self_update() -> str:
"""Upgrade the installed package without touching the active project."""
if shutil.which("uv") is None:
uv_path = shutil.which("uv")
if uv_path is None:
return (
"OpenKyrozen is package-managed. Install or update it with the official "
f"{RELEASE_TAG} installer (uv is required)."
Expand All @@ -1307,7 +1308,7 @@ def _self_update() -> str:
if sys.version_info[:2] in {(3, 12), (3, 13)} else "3.12"
)
command = [
"uv", "tool", "install", "--python", requested_python, "--force",
uv_path, "tool", "install", "--python", requested_python, "--force",
"--with", "fastapi", "--with", "uvicorn", RELEASE_WHEEL_URL,
]
try:
Expand All @@ -1319,18 +1320,25 @@ def _self_update() -> str:
)
if result.returncode != 0:
retry = subprocess.run(
["uv", "--no-cache", *command[1:]],
[uv_path, "--no-cache", *command[1:]],
capture_output=True,
text=True,
timeout=60,
)
if retry.returncode == 0:
result = retry
result = retry
out = result.stdout.strip() or ""
err = result.stderr.strip() or ""
diagnostics = "\n".join(part for part in (out, err) if part)
if result.returncode != 0:
return f"Update failed:\n{err}"
return f"Updated OpenKyrozen from GitHub release {RELEASE_TAG}:\n{out}"
return (
f"Update failed (uv exit {result.returncode}):\n"
f"{diagnostics or 'uv returned no diagnostics.'}"
)
return (
f"Updated OpenKyrozen from GitHub release {RELEASE_TAG}:\n"
f"{diagnostics or 'uv completed successfully.'}\n"
"Restart kyrozen to use the updated process."
)
except subprocess.TimeoutExpired:
return "Update timed out."
except FileNotFoundError:
Expand Down
21 changes: 19 additions & 2 deletions tests/test_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def test_update_uses_the_package_manager_instead_of_git_pull(self):
f"{sys.version_info.major}.{sys.version_info.minor}"
if sys.version_info[:2] in {(3, 12), (3, 13)} else "3.12"
)
self.assertEqual(command[:6], ["uv", "tool", "install", "--python", expected_python, "--force"])
self.assertEqual(command[:6], ["/usr/local/bin/uv", "tool", "install", "--python", expected_python, "--force"])
self.assertIn("--with", command)
self.assertIn("fastapi", command)
self.assertIn("uvicorn", command)
Expand All @@ -265,9 +265,26 @@ def test_update_retries_without_uv_cache_after_install_failure(self):
self.assertEqual(run.call_count, 2)
self.assertEqual(
run.call_args_list[1].args[0][:3],
["uv", "--no-cache", "tool"],
["/usr/local/bin/uv", "--no-cache", "tool"],
)

def test_update_preserves_stdout_only_failure_diagnostics(self):
import main

failed = subprocess.CompletedProcess(
["/usr/local/bin/uv", "tool", "install"], 1,
stdout="release asset unavailable", stderr="",
)
retry_failed = subprocess.CompletedProcess(
["/usr/local/bin/uv", "--no-cache", "tool", "install"], 1,
stdout="retry also failed", stderr="",
)
with patch("main.shutil.which", return_value="/usr/local/bin/uv"), \
patch("main.subprocess.run", side_effect=[failed, retry_failed]):
result = main._self_update()
self.assertIn("uv exit 1", result)
self.assertIn("retry also failed", result)

def test_docker_starts_server_in_explicit_project_mode(self):
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
self.assertIn('"server.py", "--project", "/app"', dockerfile)
Expand Down
Loading