From fac25e56e0f31f7cb246ea484ebd3a79d9b1a24f Mon Sep 17 00:00:00 2001 From: yujiezhang-ops Date: Tue, 4 Aug 2026 17:37:47 +0800 Subject: [PATCH 1/3] fix: restore the files 4d63d75 deleted while leaving their references `chore: remove unused files` deleted three icon assets and nine catalog entries but did not touch the code reading them, so main could not build or pass its tests. `go vet` stayed clean, which is why it went unnoticed: only the build and the test run reach these paths. The three assets are restored because agents.tsx never stopped importing them, and icons/agents.test.tsx asserts a distinct mark for cursor, openclaw and hermes -- it calls them PROMINENT_GUIDE_AGENTS, agents shown on the first screen alongside the one-click ones. That contract was not part of the cleanup. Those same three catalog entries come back, at ranks 6-8 rather than their old 3/5/6, since main renumbered the auto agents to 1-5 and the old values would now collide. Guide-only is not a dead concept: internal/app/install.go, catalog/manifest.go validation, AgentRow and ReviewPage all still branch on it, and provider_test.go and cmd/oneagent/main_test.go both use openclaw to exercise it. The six entries with no code referencing them -- kiro, gemini-cli, cline, continue, qwen-code, kilo-vscode -- stay deleted. The golden fixture is regenerated rather than hand-edited, so it comes from the same GetStatus call the assertion reads. It reorders more lines than it changes because Go marshals maps with sorted keys and the previous file was not written that way; the substance is six agents leaving and three staying. frontend/dist/.keep is added because it was never actually tracked. The .gitignore comment describes it as the thing that makes `go:embed all:frontend/dist` work on a fresh clone, and the negation on line 14 is right, but with no committed file the pattern had nothing to match -- so `go vet` on a clean checkout failed before any of the above could be seen. Co-Authored-By: Claude Fable 5 --- agents.lock.json | 39 ++ frontend/dist/.keep | 0 .../src/components/icons/assets/cursor.svg | 1 + .../src/components/icons/assets/hermes.png | Bin 0 -> 4100 bytes .../src/components/icons/assets/openclaw.svg | 60 +++ .../testdata/status-empty-linux-arm64.json | 352 ++++-------------- 6 files changed, 177 insertions(+), 275 deletions(-) create mode 100644 frontend/dist/.keep create mode 100644 frontend/src/components/icons/assets/cursor.svg create mode 100644 frontend/src/components/icons/assets/hermes.png create mode 100644 frontend/src/components/icons/assets/openclaw.svg diff --git a/agents.lock.json b/agents.lock.json index 02fc7b3..241ad64 100644 --- a/agents.lock.json +++ b/agents.lock.json @@ -132,6 +132,45 @@ "windows" ], "rank": 5 + }, + "cursor": { + "name": "Cursor", + "group": "platform", + "command": "cursor", + "config_mode": "guide", + "platforms": [ + "macos", + "linux", + "windows" + ], + "guide": "Use Cursor subscription or official account settings; OneAgent does not write a private Cursor configuration.", + "rank": 6 + }, + "openclaw": { + "name": "OpenClaw", + "group": "gateway", + "command": "openclaw", + "config_mode": "guide", + "platforms": [ + "macos", + "linux", + "windows" + ], + "guide": "Install from OpenClaw docs, then run: openclaw onboard --install-daemon && openclaw gateway status", + "rank": 7 + }, + "hermes": { + "name": "Hermes", + "group": "gateway", + "command": "hermes", + "config_mode": "guide", + "platforms": [ + "macos", + "linux", + "windows" + ], + "guide": "Install Hermes, then configure models with hermes model or ~/.hermes/config.yaml.", + "rank": 8 } } } diff --git a/frontend/dist/.keep b/frontend/dist/.keep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/components/icons/assets/cursor.svg b/frontend/src/components/icons/assets/cursor.svg new file mode 100644 index 0000000..f814fdc --- /dev/null +++ b/frontend/src/components/icons/assets/cursor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/icons/assets/hermes.png b/frontend/src/components/icons/assets/hermes.png new file mode 100644 index 0000000000000000000000000000000000000000..2d629d2f2452f00a7bc5a3252c1c1829c0b476bd GIT binary patch literal 4100 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/app/testdata/status-empty-linux-arm64.json b/internal/app/testdata/status-empty-linux-arm64.json index 61fae6e..65a5bf5 100644 --- a/internal/app/testdata/status-empty-linux-arm64.json +++ b/internal/app/testdata/status-empty-linux-arm64.json @@ -7,20 +7,21 @@ }, "capabilities": { "canInstall": { - "codex": false, + "aider": false, "claude-code": false, - "opencode": false, + "codex": false, + "cursor": false, + "hermes": false, "kilo-cli": false, - "aider": false, "openclaw": false, - "hermes": false, - "cursor": false, - "kiro": false, - "gemini-cli": false, - "cline": false, - "continue": false, - "qwen-code": false, - "kilo-vscode": false + "opencode": false + }, + "missingRuntime": { + "aider": "uv", + "claude-code": "node", + "codex": "node", + "kilo-cli": "node", + "opencode": "node" }, "supportedAgentIds": [ "codex", @@ -28,234 +29,131 @@ "opencode", "kilo-cli", "aider", - "openclaw", - "hermes", "cursor", - "kiro", - "gemini-cli", - "cline", - "continue", - "qwen-code", - "kilo-vscode" - ], - "missingRuntime": { - "aider": "uv", - "claude-code": "node", - "codex": "node", - "kilo-cli": "node", - "opencode": "node" - } + "openclaw", + "hermes" + ] }, "agents": { - "codex": { + "aider": { "installed": false, - "config": "${HOME}/.codex/config.toml", "configured": false, "guideOnly": false, + "config": "${HOME}/.oneagent/aider.env", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, "claude-code": { "installed": false, - "config": "${HOME}/.claude/settings.json", - "configured": false, - "guideOnly": false, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null - }, - "opencode": { - "installed": false, - "config": "${HOME}/.config/opencode/opencode.json", - "configured": false, - "guideOnly": false, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null - }, - "kilo-cli": { - "installed": false, - "config": "${HOME}/.config/kilo/kilo.jsonc", "configured": false, "guideOnly": false, + "config": "${HOME}/.claude/settings.json", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, - "aider": { + "codex": { "installed": false, - "config": "${HOME}/.oneagent/aider.env", "configured": false, "guideOnly": false, + "config": "${HOME}/.codex/config.toml", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null - }, - "openclaw": { - "installed": false, - "config": "", - "configured": false, - "guideOnly": true, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null - }, - "hermes": { - "installed": false, - "config": "", - "configured": false, - "guideOnly": true, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, "cursor": { "installed": false, - "config": "", "configured": false, "guideOnly": true, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null - }, - "kiro": { - "installed": false, "config": "", - "configured": false, - "guideOnly": true, "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, - "gemini-cli": { + "hermes": { "installed": false, - "config": "", "configured": false, "guideOnly": true, - "version": null, - "lockedVersion": null, - "canInstall": false, - "provider": null, - "model": null, - "baseUrl": null, - "updatedAt": null, - "detected": null, - "profileId": null - }, - "cline": { - "installed": false, "config": "", - "configured": false, - "guideOnly": true, "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, - "continue": { + "kilo-cli": { "installed": false, - "config": "", "configured": false, - "guideOnly": true, + "guideOnly": false, + "config": "${HOME}/.config/kilo/kilo.jsonc", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, - "qwen-code": { + "openclaw": { "installed": false, - "config": "", "configured": false, "guideOnly": true, + "config": "", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null }, - "kilo-vscode": { + "opencode": { "installed": false, - "config": "", "configured": false, - "guideOnly": true, + "guideOnly": false, + "config": "${HOME}/.config/opencode/opencode.json", "version": null, "lockedVersion": null, "canInstall": false, "provider": null, + "profileId": null, "model": null, "baseUrl": null, "updatedAt": null, - "detected": null, - "profileId": null + "detected": null } }, "catalog": [ @@ -291,22 +189,6 @@ "platformNote": "", "rank": 2 }, - { - "id": "cursor", - "name": "Cursor", - "group": "platform", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 3 - }, { "id": "opencode", "name": "OpenCode", @@ -321,55 +203,7 @@ "windows" ], "platformNote": "", - "rank": 4 - }, - { - "id": "openclaw", - "name": "OpenClaw", - "group": "gateway", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 5 - }, - { - "id": "hermes", - "name": "Hermes", - "group": "gateway", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 6 - }, - { - "id": "gemini-cli", - "name": "Gemini CLI", - "group": "platform", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 7 + "rank": 3 }, { "id": "kilo-cli", @@ -385,7 +219,7 @@ "windows" ], "platformNote": "", - "rank": 8 + "rank": 4 }, { "id": "aider", @@ -401,11 +235,11 @@ "windows" ], "platformNote": "", - "rank": 9 + "rank": 5 }, { - "id": "kiro", - "name": "Kiro", + "id": "cursor", + "name": "Cursor", "group": "platform", "configMode": "guide", "guideOnly": true, @@ -417,44 +251,12 @@ "windows" ], "platformNote": "", - "rank": 10 - }, - { - "id": "cline", - "name": "Cline", - "group": "ide", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 11 - }, - { - "id": "continue", - "name": "Continue", - "group": "ide", - "configMode": "guide", - "guideOnly": true, - "lockedVersion": null, - "protocol": null, - "platforms": [ - "macos", - "linux", - "windows" - ], - "platformNote": "", - "rank": 12 + "rank": 6 }, { - "id": "qwen-code", - "name": "Qwen Code", - "group": "ide", + "id": "openclaw", + "name": "OpenClaw", + "group": "gateway", "configMode": "guide", "guideOnly": true, "lockedVersion": null, @@ -465,12 +267,12 @@ "windows" ], "platformNote": "", - "rank": 13 + "rank": 7 }, { - "id": "kilo-vscode", - "name": "Kilo VS Code", - "group": "ide", + "id": "hermes", + "name": "Hermes", + "group": "gateway", "configMode": "guide", "guideOnly": true, "lockedVersion": null, @@ -481,7 +283,7 @@ "windows" ], "platformNote": "", - "rank": 14 + "rank": 8 } ], "groups": [ @@ -503,17 +305,17 @@ } ], "providers": { - "ppio": { - "name": "PPIO", - "home": "https://ppio.com/", - "base_url": "https://api.ppio.com/openai", - "anthropic_base_url": "https://api.ppio.com/anthropic" - }, "novita": { "name": "Novita", "home": "https://novita.ai/", "base_url": "https://api.novita.ai/openai", "anthropic_base_url": "https://api.novita.ai/anthropic" + }, + "ppio": { + "name": "PPIO", + "home": "https://ppio.com/", + "base_url": "https://api.ppio.com/openai", + "anthropic_base_url": "https://api.ppio.com/anthropic" } }, "mirrors": [ @@ -533,26 +335,25 @@ } ], "paths": { - "logs": "${HOME}/.oneagent/logs", - "profile": "${HOME}/.oneagent/profile.json", - "codex_config": "${HOME}/.codex/config.toml", + "aider_config": "${HOME}/.oneagent/aider.env", "claude-code_config": "${HOME}/.claude/settings.json", - "opencode_config": "${HOME}/.config/opencode/opencode.json", + "codex_config": "${HOME}/.codex/config.toml", "kilo-cli_config": "${HOME}/.config/kilo/kilo.jsonc", - "aider_config": "${HOME}/.oneagent/aider.env" + "logs": "${HOME}/.oneagent/logs", + "opencode_config": "${HOME}/.config/opencode/opencode.json", + "profile": "${HOME}/.oneagent/profile.json" }, "backups": { - "codex": false, + "aider": false, "claude-code": false, - "opencode": false, + "codex": false, "kilo-cli": false, - "aider": false, + "opencode": false, "profile": false }, "profiles": [], "activeProfile": null, - "environment": null, - "environmentError": null, + "firstRun": true, "runtimes": [ { "id": "node", @@ -587,6 +388,8 @@ "requiredByHint": "Aider" } ], + "environment": null, + "environmentError": null, "desktopAgent": { "id": "desktop-agent", "name": "ChatGPT Desktop", @@ -596,6 +399,5 @@ "source": "unknown", "configPath": "${HOME}/.codex/config.toml", "configSharedWith": "Codex" - }, - "firstRun": true + } } From ba79c3da2911ecbc8541d30951b345f2ab65b5f7 Mon Sep 17 00:00:00 2001 From: yujiezhang-ops Date: Tue, 4 Aug 2026 17:40:24 +0800 Subject: [PATCH 2/3] ci: run tests and the build on every pull request The repository had no PR checks at all -- `gh pr checks` reported none, and build-artifacts.yml is workflow_dispatch-only and builds without testing. So the break this branch repairs reached main unopposed. Two jobs, matching what a developer runs locally: go vet plus go test -race, and the frontend test run plus build. The frontend build is the step that would have caught 4d63d75, since it resolves the imports that commit orphaned; `go vet` would not have, and did not. -race rather than a plain run because the write coordination lock in internal/app is not exercised otherwise. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ac4050d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +# build-artifacts.yml is workflow_dispatch-only and builds without testing, so +# nothing ran on a pull request. That is how 4d63d75 landed on main having +# deleted three imported assets: `go vet` stays clean, and only a build or a +# test run reaches those paths. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + go: + name: Go + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + + - run: go vet ./... + + # -race because the write coordination lock in internal/app is the kind of + # thing a plain run will not exercise. + - run: go test -race ./... + + frontend: + name: Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + with: + version: 11.17.0 + + - uses: actions/setup-node@v7 + with: + node-version: 26 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + + - name: Install dependencies + working-directory: frontend + run: pnpm install --frozen-lockfile + + - name: Test + working-directory: frontend + run: pnpm run test + + # Runs tsc --noEmit before vite, so a type error fails here rather than in + # a release build. This is the step that would have caught 4d63d75. + - name: Build + working-directory: frontend + run: pnpm run build From 2deea18eb4c1ecd8b908fce4300e719079ae4201 Mon Sep 17 00:00:00 2001 From: yujiezhang-ops Date: Tue, 4 Aug 2026 17:49:00 +0800 Subject: [PATCH 3/3] test: stop the region probe test depending on the host OS The new CI run is the first time `go test` has run on Linux in this repository, and it failed on a test nobody had touched: TestTheRegionProbeRunsOnlyOnce, "region probe ran 5 times, want 1". The production code was right. countingRunner delegated to a real process.Runner, while the caller simulates darwin -- so the argv is `defaults read -g AppleLocale`. On a developer's Mac that succeeds and the answer is cached once, which is what the test asserts. On Linux `defaults` does not exist, so the probe reports "unanswered", and detectChineseRegion deliberately does not cache an unanswered probe: it retried five times, exactly as designed. countingRunner now answers the probe itself, so the test measures caching rather than which machine it runs on. Verified it still detects the regression it exists for: disabling the `if answered` cache in settings.go brings back "ran 5 times, want 1". Co-Authored-By: Claude Fable 5 --- internal/app/settings_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/app/settings_test.go b/internal/app/settings_test.go index 73ad543..3c8b2bd 100644 --- a/internal/app/settings_test.go +++ b/internal/app/settings_test.go @@ -342,11 +342,19 @@ type countingRunner struct { runs int } -func (r *countingRunner) Run(ctx context.Context, argv []string, env map[string]string, timeout time.Duration) (process.Result, error) { +// Answers the probe itself rather than delegating to a real process. Delegating +// made the result depend on the host: the caller simulates macOS, so the argv is +// `defaults read -g AppleLocale`, which succeeds on a developer's Mac and is not +// a command at all on Linux. A probe that cannot run reports "unanswered" and is +// retried by design, so on Linux the count was 5 rather than 1 -- the assertion +// failed while the caching it describes was working correctly. +func (r *countingRunner) Run(context.Context, []string, map[string]string, time.Duration) (process.Result, error) { r.runs++ - return r.Runner.Run(ctx, argv, env, timeout) + return process.Result{ExitCode: 0, Stdout: "en_US\n"}, nil } +func (r *countingRunner) LookPath(string) (string, bool) { return "", false } + // The same preference has to reach npm, not just the runtime download. The fake // runner records every environment, so the assertion is the registry npm was // actually pointed at.