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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
- '.github/actions/**'
web:
- 'apps/web/ui/**'
- 'apps/desktop/**'
- 'scripts/check_conversation_selection.cjs'
- '.github/workflows/ci.yml'
- '.github/actions/**'

Expand Down Expand Up @@ -87,6 +89,12 @@ jobs:
working-directory: apps/web/ui
run: npm ci

- name: Check shared settings and client preferences
if: ${{ !cancelled() && steps.ui-deps.outcome == 'success' }}
run: |
node --test apps/desktop/test/policy.test.cjs
node scripts/check_conversation_selection.cjs

- name: Check TypeScript
id: typecheck
if: ${{ !cancelled() && steps.ui-deps.outcome == 'success' }}
Expand Down
145 changes: 141 additions & 4 deletions .github/workflows/release-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ jobs:
TAG="${TAG#refs/tags/}"
TAG="${TAG//\//-}"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Check settings interface policy before release
run: node --test apps/desktop/test/policy.test.cjs
- name: Build application bundle
run: python scripts/build_release_bundle.py --version "${{ steps.version.outputs.version }}" --repository "${{ github.repository }}"
- uses: actions/upload-artifact@v4
Expand All @@ -68,6 +73,7 @@ jobs:

images:
name: ${{ matrix.image }} (${{ matrix.target.arch }})
needs: bundle
if: ${{ github.run_attempt == 1 && github.run_number <= fromJSON(vars.MUTEKI_RELEASE_LAST_RUN_NUMBER || '0') }}
timeout-minutes: 30
strategy:
Expand Down Expand Up @@ -270,6 +276,131 @@ jobs:
if-no-files-found: error
retention-days: 7

desktop-macos:
name: Muteki Desktop macOS (${{ matrix.target.arch }})
needs: bundle
if: ${{ github.run_attempt == 1 && github.run_number <= fromJSON(vars.MUTEKI_RELEASE_LAST_RUN_NUMBER || '0') }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
target:
- arch: arm64
runner: macos-15
- arch: x64
runner: macos-15-intel
runs-on: ${{ matrix.target.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24.21.0"
cache: npm
cache-dependency-path: |
apps/desktop/package-lock.json
apps/web/ui/package-lock.json
- uses: actions/setup-python@v5
with:
python-version: "3.13.13"
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: uv.lock
- name: Verify runner architecture
env:
EXPECTED_ARCH: ${{ matrix.target.arch }}
run: |
set -euo pipefail
case "$EXPECTED_ARCH:$(uname -m)" in
arm64:arm64|x64:x86_64) ;;
*) echo "Runner architecture does not match $EXPECTED_ARCH" >&2; exit 1 ;;
esac
- name: Install desktop dependencies
working-directory: apps/desktop
run: npm ci
- name: Install web dependencies
working-directory: apps/web/ui
run: npm ci
- name: Check desktop
working-directory: apps/desktop
run: |
npm test
npm run typecheck
npm run build
- name: Build relocatable desktop runtime
run: python scripts/build_desktop_runtime.py
- name: Package macOS desktop release
working-directory: apps/desktop
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
run: npx electron-builder --mac dmg zip --${{ matrix.target.arch }} --publish never -c.mac.identity=-
- name: Verify packaged application
env:
EXPECTED_ARCH: ${{ matrix.target.arch }}
run: |
set -euo pipefail
app="$(find apps/desktop/dist -maxdepth 3 -name Muteki.app -print -quit)"
test -n "$app"
codesign --verify --deep --strict "$app"
file "$app/Contents/Resources/native/macos-speech.node"
case "$EXPECTED_ARCH" in
arm64) file "$app/Contents/Resources/runtime/node" | grep -F 'arm64' ;;
x64) file "$app/Contents/Resources/runtime/node" | grep -F 'x86_64' ;;
esac
test -n "$(find apps/desktop/dist -maxdepth 1 -name '*.dmg' -print -quit)"
test -n "$(find apps/desktop/dist -maxdepth 1 -name '*.zip' -print -quit)"
- name: Run packaged desktop acceptance check
run: |
set -euo pipefail
python3 - <<'PY'
import json
import os
from pathlib import Path
import subprocess

apps = list(Path("apps/desktop/dist").glob("mac*/Muteki.app"))
if len(apps) != 1:
raise SystemExit(f"Expected one packaged Muteki.app, found {apps!r}")
app = apps[0]
expected_version = json.loads(Path("apps/desktop/package.json").read_text())["version"]
profile = Path(os.environ["RUNNER_TEMP"]) / "muteki-desktop-acceptance"
profile.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["MUTEKI_DESKTOP_USER_DATA"] = str(profile / "desktop")
env["MUTEKI_DESKTOP_REGISTER_PROTOCOL"] = "0"
for key in (
"MUTEKI_DESKTOP_URL", "MUTEKI_ENVIRONMENT_ROOT", "MUTEKI_DESKTOP_WORKSPACE",
"NODE_OPTIONS", "NODE_PATH", "ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV",
):
env.pop(key, None)
executable = app / "Contents/MacOS/Muteki"
log_path = profile / "acceptance.log"
try:
with log_path.open("wb") as log:
result = subprocess.run(
[str(executable), "--muteki-channel=candidate", f"--muteki-environment-root={profile}", "--muteki-self-check"],
stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, env=env, timeout=180, check=False,
)
except subprocess.TimeoutExpired:
tail = log_path.read_text(errors="replace")[-12000:]
raise SystemExit(f"Packaged app acceptance exceeded 180 seconds\\n{tail}")
result_path = profile / "candidate-result.json"
payload = json.loads(result_path.read_text()) if result_path.exists() else {}
if result.returncode != 0 or payload.get("status") != "passed" or payload.get("version") != expected_version:
tail = log_path.read_text(errors="replace")[-12000:]
raise SystemExit(f"Packaged app acceptance failed (exit {result.returncode}); expected version={expected_version!r}; result={payload!r}\\n{tail}")
print(f"Packaged desktop acceptance passed for version {payload.get('version')}")
PY
- name: Upload macOS desktop assets
uses: actions/upload-artifact@v4
with:
name: desktop-macos-${{ matrix.target.arch }}
path: |
apps/desktop/dist/*.dmg
apps/desktop/dist/*.zip
if-no-files-found: error
retention-days: 7

worker-base:
name: muteki-worker base (${{ matrix.target.arch }})
timeout-minutes: 45
Expand Down Expand Up @@ -685,15 +816,21 @@ jobs:

publish:
timeout-minutes: 5
needs: [bundle, merge, merge-worker]
needs: [bundle, images, desktop-macos, merge, merge-worker]
# Tag push only — workflow_dispatch must never create a formal GitHub Release.
if: ${{ always() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && needs.bundle.result == 'success' && needs.merge.result == 'success' && needs.merge-worker.result == 'success' }}
if: ${{ always() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && needs.bundle.result == 'success' && needs.images.result == 'success' && needs.desktop-macos.result == 'success' && needs.merge.result == 'success' && needs.merge-worker.result == 'success' }}
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
with:
name: muteki-release-bundle
path: dist
- name: Download macOS desktop assets
uses: actions/download-artifact@v4
with:
pattern: desktop-macos-*
path: dist/desktop
merge-multiple: true
- name: Download merged image digests
uses: actions/download-artifact@v4
with:
Expand Down Expand Up @@ -729,11 +866,11 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# Attach bundle + image-identity so the GitHub Release lines up with GHCR digests.
# Attach desktop, bundle + image-identity so the GitHub Release lines up with GHCR digests.
TAG="${{ github.ref_name }}"
RELEASE_ARGS=(--repo "${{ github.repository }}" --verify-tag --generate-notes --title "Muteki $TAG")
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
RELEASE_ARGS+=(--prerelease)
fi
gh release create "$TAG" dist/muteki-*.tar.gz dist/release-manifest.json dist/image-identity.json \
gh release create "$TAG" dist/muteki-*.tar.gz dist/release-manifest.json dist/image-identity.json dist/desktop/*.dmg dist/desktop/*.zip \
"${RELEASE_ARGS[@]}"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,5 @@ data/
/docker/tsec-hosted/.build-ctx/

/apps/web/ui/.next-readme/
/apps/web/ui/.next-desktop/
/apps/desktop/dist-dev/
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

All notable public release changes are tracked here.

## 0.4.1 - 2026-10-09

### Added

- Added macOS desktop packages for Apple silicon and Intel, with an isolated local workspace, bundled Python/Node/Web runtime, and remote service connections.
- Added shared settings for Web and desktop, including Agent accounts, model visibility, permissions, keyboard shortcuts, notifications, and appearance preferences.
- Added provider usage and limit views, a usage ledger, notification inbox, conversation checkpoints, subagent activity, and scoped browser and computer-control tools.

### Changed

- Updated the CTF and penetration-testing workspaces with shared navigation, Worker extensions, evidence-backed reports, and clearer report printing and export.
- Improved Agent compatibility, model discovery, session recovery, approvals, and process cleanup across supported runtimes.
- Refined conversation navigation, drag-and-drop ordering, message rendering, previews, and context controls. The default Web view continues to show CTF and penetration testing; conversation and competition workspaces remain optional.
- Release versions and default image tags now use `0.4.1` / `v0.4.1`. Container images continue to support Linux amd64 and arm64; desktop packages in this release are macOS only.

### Fixed

- Fixed the Web interface freezing while deleting an Agent credential and detaching its conversation and Worker references, as reported with Ubuntu container deployments ([#199](https://github.com/FishCodeTech/muteki/issues/199)).
- Fixed desktop notification ownership, permission persistence, foreground read tracking, and controlled overlay interactions.
- Fixed Worker settings navigation, stale draft runtime panels, local directory error feedback, and findings counts for older penetration-testing reports.

### Upgrade

- Use `muteki upgrade v0.4.1` or `./run.sh upgrade v0.4.1` for a managed Web installation, and add `--compose` for a container deployment. Existing `.env`, `state/`, credentials, and sessions remain outside the release bundle.
- Download the macOS `.dmg` or `.zip` matching your Mac from the release page. Windows and Linux desktop packages are deferred.
- The macOS packages are ad-hoc signed and are not notarized. Install desktop updates manually; the signed-candidate update flow requires a stable Developer ID signature.

## 0.4.0 - 2026-09-28

### Added
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</p>

<p align="center">
<strong>English</strong> · <a href="README_CN.md">简体中文</a> · <a href="CHANGELOG.md">Changelog</a>
<strong>English</strong> · <a href="README_CN.md">简体中文</a> · <a href="CHANGELOG.md">Changelog</a> · <a href="docs/development.md">Dev build guide</a>
</p>

<p align="center">
Expand Down Expand Up @@ -74,13 +74,17 @@ After a month of engineering work, tuning, and fixes, Muteki is open source with

## Release notes

The current version is **0.4.0**. See [CHANGELOG.md](CHANGELOG.md) for new features, behavior changes, removals, and upgrade notes.
The current version is **0.4.1**. See [CHANGELOG.md](CHANGELOG.md) for new features, behavior changes, removals, and upgrade notes.

The [0.4.1 release](https://github.com/FishCodeTech/muteki/releases/tag/v0.4.1) includes a macOS desktop app for Apple silicon and Intel, with a bundled local service and support for remote Muteki services. Windows and Linux desktop packages are not included in this release. Web and container deployments remain available.

The macOS packages are ad-hoc signed and are not notarized. Install desktop updates manually; the in-app signed-candidate update flow requires a stable Developer ID signature.

---

## Quick start

> **Start with the single-challenge CTF workspace.** The home page shows this workspace by default. Conversation, competitions, and custom extensions are still being tested and can be enabled as described below. The Web pentest mode is being reworked and is currently unavailable.
> **Start with the CTF or penetration-testing workspace.** Both appear on the home page by default. Conversation and competition workspaces are optional and can be enabled in appearance settings as described below.

### 1. Prepare the environment and start the Web app

Expand Down Expand Up @@ -291,7 +295,7 @@ The UI remains at `http://localhost:3001` by default. `MUTEKI_HOST_DATA_ROOT` mu
```bash
./run.sh upgrade --check # Check the latest stable release
./run.sh install # Set up a managed installation
muteki upgrade v0.4.0 # Install this release
muteki upgrade v0.4.1 # Install this release
muteki rollback # Return to the previous installed release
muteki version # Show version and installation kind
```
Expand Down
12 changes: 8 additions & 4 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</p>

<p align="center">
<a href="README.md">English</a> · <strong>简体中文</strong> · <a href="CHANGELOG.md">版本更新</a>
<a href="README.md">English</a> · <strong>简体中文</strong> · <a href="CHANGELOG.md">版本更新</a> · <a href="docs/development.md">开发版编译与更新</a>
</p>

<p align="center">
Expand Down Expand Up @@ -95,13 +95,17 @@ tsecbench 托管模式下+deepseek-flash,排名13

## 版本更新

当前版本为 **0.4.0**。新增功能、行为变化、移除内容及升级说明见 [CHANGELOG.md](CHANGELOG.md)。
当前版本为 **0.4.1**。新增功能、行为变化、移除内容及升级说明见 [CHANGELOG.md](CHANGELOG.md)。

[0.4.1 发布页](https://github.com/FishCodeTech/muteki/releases/tag/v0.4.1) 提供 Apple 芯片和 Intel Mac 的 macOS 客户端,内置本地服务,也可连接远程 Muteki 服务。本次暂不提供 Windows、Linux 桌面安装包,Web 和容器部署继续支持。

本次 macOS 安装包使用临时签名,尚未公证。桌面更新请手动安装;应用内候选版本更新流程需要稳定的 Developer ID 签名。

---

## 快速开始

> **当前优先体验:CTF 单题模式。** 首页默认只显示“单题任务”;对话、比赛及自定义插件仍在测试中,按下文步骤开启。Web 页面的“渗透”模式正在重写,当前不可选。
> **从 CTF 或渗透测试工作区开始。** 首页默认显示这两个工作区。对话和比赛工作区可按下文说明,在外观设置中开启。

### 1. 准备环境并启动

Expand Down Expand Up @@ -315,7 +319,7 @@ MUTEKI_WEB_PASSWORD='请替换为强密码' \
```bash
./run.sh upgrade --check # 检查稳定版
./run.sh install # 安装托管版本
muteki upgrade v0.4.0 # 更新到本次版本
muteki upgrade v0.4.1 # 更新到本次版本
muteki rollback # 回滚到上一个已安装版本
muteki version # 查看版本与安装形态
```
Expand Down
21 changes: 21 additions & 0 deletions apps/desktop/electron-builder.dev.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const config = require('./package.json').build;
const path = require('node:path');
const { resolveDevSigningIdentity, requireDevSigningIdentity } = require('./scripts/dev-signing.cjs');
const signingIdentity = process.platform === 'darwin'
? resolveDevSigningIdentity(path.resolve(__dirname, '../../state/_secrets/mac-signing.json'))
: undefined;
module.exports = {
...config,
appId: 'tech.fishcode.muteki.desktop.dev',
productName: 'Muteki Dev',
extraMetadata: { mutekiChannel: 'dev', main: 'src/dev-bootstrap.cjs', mutekiWorkspace: path.resolve(__dirname, '../..') },
protocols: [],
mac: { ...config.mac, identity: signingIdentity, forceCodeSigning: true },
beforePack: async context => {
if (context.electronPlatformName !== 'darwin') return;
const { findIdentity } = require('app-builder-lib/out/codeSign/macCodeSign');
const { keychainFile } = await context.packager.codeSigningInfo.value;
await requireDevSigningIdentity(signingIdentity, keychainFile, findIdentity);
},
directories: { output: 'dist-dev' },
};
Loading
Loading