feat: 完成首次贡献指南及三项任务修复#1
Conversation
* fix(feishu): suppress duplicate final replies in desktop runtime * fix(feishu): bridge reply failure events to Nexu fallback * wip(feishu): template Nexu fallback replies * fix(desktop): persist locale for fallback templates * fix(controller): merge openclaw restart and event bridge logic * chore(feishu): refine synthetic fallback diagnostics * chore(feishu): polish fallback hint rendering * fix(feishu): honor locale in fallback hints
Made-with: Cursor
…runtime improvements (#326) - Add WeChat personal channel via QR code scan (openclaw-weixin plugin) - Controller: QR start/wait/connect routes, config store, binding compiler - Frontend: WechatSetupView with infinite retry, fake progress, gateway readiness - Fix accountId normalization mismatch between Nexu and gateway - Refactor inline model selector into reusable ModelPickerDropdown - Add OpenClaw auth profiles writer for multi-account credential sync - Add desktop runtime guide and observability improvements Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: stabilize openclaw-weixin plugin entry config * fix(desktop): dereference pnpm symlinks before electron-builder pnpm creates symlinks in node_modules pointing to .pnpm directory. When electron-builder copies these as extraResources, the symlinks point to non-existent paths in the final .app bundle, causing codesign to fail with "invalid destination for symbolic link". This fix dereferences node_modules/sharp and node_modules/@img symlinks before running electron-builder. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: format dist-mac.mjs * fix(desktop): copy @img from sharp's node_modules for pnpm --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove router workspace * fix: align desktop cloud config with online env * fix(desktop): show NEXU_HOME in diagnostics * fix(controller): override Link user-agent for desktop cloud requests * fix(desktop): align dev logs path handling
* fix: preserve pnpm @img path before dereferencing sharp * fix: restore mkdir import in desktop dist script * fix: dereference controller static symlinks for desktop builds
* chore: refresh runtime lockfiles Keep the repo-local runtime lockfiles aligned with the current package manager metadata so local and packaged installs stay consistent. * chore: remove openclaw runtime preinstall cleanup Avoid running the runtime clean step before every install so workspace installs keep existing runtime artifacts intact and rely on the postinstall refresh flow instead.
… (#334) - Replace all x.com/Nexu06 links with x.com/nexudotio across README, docs, and templates - Rewrite WeChat channel doc to match Feishu/Slack/Discord style (3-step flow) Made-with: Cursor
…336) * fix: stabilize openclaw-weixin plugin entry config * fix(desktop): dereference pnpm symlinks before electron-builder pnpm creates symlinks in node_modules pointing to .pnpm directory. When electron-builder copies these as extraResources, the symlinks point to non-existent paths in the final .app bundle, causing codesign to fail with "invalid destination for symbolic link". This fix dereferences node_modules/sharp and node_modules/@img symlinks before running electron-builder. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: format dist-mac.mjs * fix(desktop): copy @img from sharp's node_modules for pnpm * fix(desktop): find @img as sibling of sharp in pnpm structure * fix(desktop): resolve pnpm paths before dereferencing * fix(desktop): use cp -RL for full symlink dereference in pnpm deps Node.js fs.cp with dereference:true only dereferences the top-level source path, not nested symlinks inside directories. The @img package contains symlinks to other pnpm packages that were not being resolved. Switch to cp -RL shell command which properly resolves all nested symlinks recursively. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * debug: add symlink detection after dereference * fix(desktop): dereference ALL pnpm symlinks before electron-builder Previous fix only handled sharp and @img, but pnpm creates symlinks for all dependencies. Now we find and dereference all symlinks in node_modules before electron-builder runs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(desktop): isolate sidecar patching and refresh model inventory * revert: remove redundant symlink fix, use upstream solution from PR #330 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * revert: restore package-lock.json to main --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Rewrite the WeChat channel guide into a 7-step hands-on tutorial covering download, install, login, channel selection, QR-code scan, connection confirmation, and chatting in WeChat. Add 8 screenshot assets and register WeChat in the VitePress sidebar for both EN/ZH. Also expand the MIT license section in both READMEs. Made-with: Cursor
* chore(desktop): prepare v0.1.4 * fix(actions): preserve desktop package formatting --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Add bold callout for WeChat OpenClaw support in overview section - Add WeChat scan-to-connect screenshot side-by-side with main screenshot - Update license section copy Made-with: Cursor
The before-quit handler was fire-and-forget (void orchestrator.dispose()), so Electron exited before child processes were terminated. Additionally, stopUnit() only sent SIGTERM twice without escalating to SIGKILL, allowing processes to ignore shutdown and become orphans that block app deletion. - Await orchestrator.dispose() by blocking quit with event.preventDefault() - Escalate to SIGKILL after 3s if SIGTERM is ignored - Add 5s final deadline to prevent hanging quit
… loop (#347) The config writer unconditionally wrote the config file on every syncAll() call, even when content was unchanged. This triggered OpenClaw's file watcher, causing a reload/restart cycle: restart → WS reconnect → syncAll() → write → watcher fires → restart again. Feishu channels were most affected because their WebSocket initialization is slower than other channels, so the snapshot was never ready before the next restart. Keep the last-written content in memory and skip the write when it matches, breaking the loop after the first legitimate write.
On process restart, lastWrittenContent is null, causing the first write() to always hit disk even if the config file already has identical content. This triggers an unnecessary OpenClaw reload on every controller restart. Read the existing file to seed the in-memory cache on the first write() call, so cold starts skip the write when content matches.
* feat(desktop): allocate runtime ports dynamically * fix(desktop): stabilize patched feishu reply handling * chore(desktop): note synthetic failure follow-up
* Render markdown in session chat bubbles * Disable markdown image rendering in chat --------- Co-authored-by: NickHood <nickhood1984@gmail.com>
* docs: add launchd process architecture design Add design document for migrating Nexu Desktop to launchd-based process management on macOS. Key decisions: - Controller and OpenClaw run as independent LaunchAgents - Desktop becomes GUI shell + service orchestrator - Web UI embedded in Electron main process (no separate sidecar) - Unified logging to NEXU_LOG_DIR - Development and production both use launchd Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add English version of launchd architecture design Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: reorganize into folder + address code review feedback - Move docs into specs/designs/launchd-process-architecture/ folder - Add language specifiers to fenced code blocks - Add /openapi.json to proxy routes - Use os.userInfo().uid instead of hardcoded 501 fallback - Add isServiceRegistered() and hasPlistFile() methods - Add stopServiceGracefully() with timeout and force kill - Improve SPA fallback logic using statSync().isDirectory() - Add server error handling in startEmbeddedWebServer() - Add cleanup trap to dev script - Show bootstrap errors instead of silencing them - Add proxyToController implementation - Add DesktopEnv type definition - Add env schema reminder to checklist Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(design): address remaining CodeRabbit feedback - Add collectBody helper to properly collect request body chunks before passing to fetch (IncomingMessage can't be used directly) - Define SERVICE_LABELS constant in exit behavior snippet to fix undefined 'labels' variable reference Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(design): use async fs APIs and add bootstrap logging - Replace sync fs APIs (existsSync, statSync) with async fs/promises methods (access, stat) to avoid blocking Electron main process - Add fileExists helper for non-blocking file existence check - Add try/catch with logging around launchctl bootstrap to surface errors instead of silently failing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs(design): clarify isolation from native OpenClaw installation Add note explaining that Nexu Desktop uses com.nexu.* namespace, completely independent from OpenClaw's native io.openclaw.* labels. State directories are also separate (~/.nexu/ vs ~/.openclaw/). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(controller): seed config writer cache from disk on cold start On process restart, lastWrittenContent is null, causing the first write() to always hit disk even if the config file already has identical content. This triggers an unnecessary OpenClaw reload on every controller restart. Read the existing file to seed the in-memory cache on the first write() call, so cold starts skip the write when content matches. * feat(web): add Import Skill modal with bilingual support Replace the inline hidden file input + button with a polished modal dialog ported from the design-system. Two tabs: Upload Zip (wired to existing zip import backend) and GitHub Link (placeholder, coming soon). Adds Dialog UI component (Radix-based) and i18n keys for EN/ZH-CN. Also fixes controller startup crash (EINVAL) when copying runtime plugin dirs containing node_modules/.bin symlinks by filtering out symlinks during the recursive copy. * fix: narrow runtime plugin symlink filter * fix(web): add missing import-skill-modal-state module and tests The modal state helpers (getSelectedZipFile, createAutoCloseController) were extracted but not committed, breaking the build with TS2307. * fix(test): align plugin writer test with dereference: true behavior The test expected non-.bin symlinks to be preserved as symlinks, but the implementation uses dereference: true which materializes them into real directories. Update the test to assert directory materialization instead of symlink preservation.
* fix(desktop): support channel-aware update checks * feat(desktop): add about dialog and update menu actions * fix(desktop): show update check feedback * fix(desktop): surface update checks in packaged app * fix(desktop): remove duplicate about dialog * fix(desktop): move update checks into app menu * fix(controller): preserve custom runtime model overrides * fix(desktop): silence background update checks * fix(desktop): address updater review feedback
* fix(controller): seed config writer cache from disk on cold start On process restart, lastWrittenContent is null, causing the first write() to always hit disk even if the config file already has identical content. This triggers an unnecessary OpenClaw reload on every controller restart. Read the existing file to seed the in-memory cache on the first write() call, so cold starts skip the write when content matches. * feat(web): add Import Skill modal with bilingual support Replace the inline hidden file input + button with a polished modal dialog ported from the design-system. Two tabs: Upload Zip (wired to existing zip import backend) and GitHub Link (placeholder, coming soon). Adds Dialog UI component (Radix-based) and i18n keys for EN/ZH-CN. Also fixes controller startup crash (EINVAL) when copying runtime plugin dirs containing node_modules/.bin symlinks by filtering out symlinks during the recursive copy. * fix: narrow runtime plugin symlink filter * fix(web): add missing import-skill-modal-state module and tests The modal state helpers (getSelectedZipFile, createAutoCloseController) were extracted but not committed, breaking the build with TS2307. * fix(test): align plugin writer test with dereference: true behavior The test expected non-.bin symlinks to be preserved as symlinks, but the implementation uses dereference: true which materializes them into real directories. Update the test to assert directory materialization instead of symlink preservation. * fix(web): prevent skill card drag from opening a second window Disable native link drag on SkillCard <Link> elements so that dragging a card in Electron no longer triggers a new BrowserWindow. * fix(web): show correct total count on Yours "All" sub-tab The "All" sub-tab count was derived from yourSkillsList which is filtered by the active sub-tab, so switching to "Installed" made the "All" count drop to 0. Use installedSkills.length instead.
* docs: add open-source governance files and RFC framework - Add root CONTRIBUTING.md (canonical English guide) with docs site inclusion via VitePress @@include; rewrite zh contributing guide - Add root SECURITY.md with responsible disclosure policy (email + GitHub private vulnerability reporting); link from specs/SECURITY.md - Add specs/rfcs/ framework: README template, and three draft RFCs (0001 Feishu scan-to-connect, 0002 Cloud Skills, 0003 scheduled task display) - Update docs-ci.yml to trigger on CONTRIBUTING.md changes - Add "license": "MIT" to all four workspace packages - Update README contributing sections to point to the full guides Made-with: Cursor * i18n: add Japanese localization (README + docs site) - Add README.ja.md with full Japanese translation - Add docs/ja/ with all 12 pages mirroring docs/en/ - Update VitePress config with ja locale, sidebar, search, and locale-preference script - Add Japanese language switcher links to README.md and README.zh-CN.md Made-with: Cursor * fix: address CodeRabbit review comments - Add `text` language identifier to fenced code blocks (MD040) - Add alt text to contributors image in README.ja.md (MD045) - Fix ja locale redirect: map preferred locale "ja" to /ja/ path Made-with: Cursor
- EN/JA/KO: add "Open Source Co-creation" recruitment block and contributor appreciation text (matching zh-CN style) - zh-CN: rename link text from "第一次提 PR 指南" to "Good First Issue 贡献者指南" - All languages mention NickHood1984 as example contributor - Contributing sections are intentionally left untouched Made-with: Cursor Co-authored-by: caiqiling958-cmd <>
- first-pr.md: add "加入贡献者交流群" section before FAQ - contributor-rewards.md: add "加入贡献者交流群" section after Good First Issue link - Add wechat-contributor-qr.png to docs/public/ Made-with: Cursor Co-authored-by: caiqiling958-cmd <>
…uides (#1043) - Create English versions: `docs/en/guide/first-pr.md` and `docs/en/guide/contributor-rewards.md` - Create Japanese versions: `docs/ja/guide/first-pr.md` and `docs/ja/guide/contributor-rewards.md` - Add sidebar entries for both languages in VitePress config Made-with: Cursor Co-authored-by: caiqiling958-cmd <>
* fix(desktop): keep a single update banner * fix(desktop): restore update progress feedback * fix(desktop): show real download progress in settings page - getStatus() now returns percent from lastProgress so the settings page gets the actual download progress on mount - Settings page polls getStatus() every 1s during background downloads instead of switching to foreground mode (avoids triggering the desktop shell banner) - Fix empty update banner when experience is local-test-feed by correcting the idle-state early return logic * fix(desktop): consolidate update UI and improve diagnostics - Remove "Check for Updates…" from application menu (single entry via settings) - Add UpdateBadge to sidebar for dismissed update visibility - Reset dismissed state on update events so new updates re-surface - Add progress listener diagnostics to mac-update-driver - Improve update card layout (fit-content width, word wrap, flex-wrap) - Mock update server: support slow streaming for real ZIP files - Export getUpdateManager() from ipc module - Pin version to 0.1.10 for nightly update testing * fix(controller): deterministic openclaw config serialization - Sort skill slugs at all sources (compiler, sync service, workspace scanner) so merged skill lists are stable regardless of input order - Introduce serializeOpenClawConfig() with sorted JSON keys so semantically identical configs produce the same output string - Config writer and gateway service use deterministic serialization to avoid unnecessary OpenClaw reloads on key reordering - Add tests for deterministic skill ordering and key-order skip * fix(desktop): remove driver-level writeLog calls that lack diagnostic arg
The version was accidentally downgraded to 0.1.10 for local nightly update testing and merged to main. Restore to 0.1.11.
* docs: rewrite README (en/zh/ja/ko) and remove Windows beta guide * docs: fix dead link to removed windows-beta guide
…1049) Replace hardcoded English strings with i18n keys: - Home page "Chat" label - Conversation page "Open Folder" button - Conversation page platform open buttons (Open in Slack/Feishu/etc.) Fixes #721 Co-authored-by: thu-lyx <thu-lyx@gmail.com>
…cs (#1054) * chore(controller): add workspace binding diagnostics for channel setup * fix(web): fix welcome locale switcher rendering and layering * fix(test): relax libtv Feishu submit timeout on windows
…051) - ZH: replace WeChat QR with Feishu QR, title "加入 nexu 开发者飞书交流群" - ZH: Discord #leaderboard → "Start your first Nexu PR" with invite link - EN/JA: replace WeChat QR with Discord contributor group invite link - EN/JA: Discord #leaderboard → "Start your first Nexu PR" with invite link - KO: create first-pr.md and contributor-rewards.md with Discord group - KO: add sidebar entries in VitePress config - Add feishu-contributor-qr.png to docs/public Made-with: Cursor Co-authored-by: caiqiling958-cmd <>
* chore(controller): add workspace binding diagnostics for channel setup * fix(web): fix welcome locale switcher rendering and layering * fix(test): relax libtv Feishu submit timeout on windows * fix(desktop): retry when Windows installer finds running Nexu
… (#1074) * fix(desktop): restore version to 0.1.11 The version was accidentally downgraded to 0.1.10 for local nightly update testing and merged to main. Restore to 0.1.11. * chore: bump desktop version to 0.1.12 * fix(controller): batch skillhub sync to prevent OpenClaw restart loop During initial bootstrap, each skill installation triggered an individual config sync → openclaw.json write → OpenClaw full process restart. With 10+ skills installing sequentially, this caused a sustained restart loop where the agent session never stabilized, leaving the UI stuck on "agent starting". Move the onSyncNeeded trigger from InstallQueue's per-item onComplete callback to a new onIdle callback that fires only when the queue fully drains. This batches N skill installs into a single config sync while preserving immediate sync for single-skill installs (queue drains right after the one item completes). * fix(controller): make langfuse-tracer always-allow to avoid gateway restart langfuse-tracer was conditionally added to plugins.allow based on analyticsEnabled. On first boot or when the preference changed, this mutated plugins.allow, which OpenClaw treats as a full gateway restart (~11s drain + process restart on Windows). Move langfuse-tracer to the unconditional always-allow list and use the entries.enabled flag to toggle it. This way analytics preference changes only flip the enabled flag (hot-reload path) instead of mutating plugins.allow (full restart path). * fix(desktop): remove capability guard from update install button The update banner's "restart & install" button silently did nothing because the renderer-side install() checked applyMode before calling IPC. If the capability was fetched before updateManager initialized, applyMode was "none" and the guard blocked the call permanently. Remove the redundant guard — the main process IPC handler already has its own null check on updateManager. The button is only visible when the update is in "ready" state, so the install action is always valid at that point.
将更新通知横幅从左侧移动到右侧,避免与侧边栏导航元素重叠。 - 修改文件: apps/desktop/src/runtime-page.css - 变更: left: 12px → right: 12px,添加 left: auto
修复bootstrapLocale函数race condition问题,当用户通过localStorage手动选择语言时,尊重用户选择,不覆盖现有设置。 - 修改文件: apps/web/src/hooks/use-locale.tsx - 新增逻辑: 在bootstrap过程中检查localStorage中的用户手动选择 - 修复问题: 欢迎页语言切换器无法改变UI语言的问题
优化Windows版本更新通知的UI设计,简化信息展示: - 修改文件: apps/desktop/src/components/update-banner.tsx - 新增功能: simplifyVersion()函数简化版本显示,移除技术时间戳 - UI优化: 简化标题文本,稳定版本只显示major.minor.patch - 用户体验: 更清晰的信息层次,对普通用户更友好
- fix: 版本更新通知设计杂乱 #1005 - fix: 欢迎页语言切换器无效 #448 - fix: 更新通知横幅遮挡侧边栏 #859 - docs: 在README中添加贡献指南提示 Contributions are welcome! The full guide is in CONTRIBUTING.md at the repo root, and published at docs.nexu.io — Contributing. Chinese: docs.nexu.io (zh) — Contributing. For Chinese-speaking contributors, we recommend starting from these entry points: - How to contribute: docs.nexu.io (zh) — Contributing - Rewards and support: 贡献奖励与支持 - Looking for a first PR? We are actively looking for Good First Issue contributors: First PR guide (zh) · good-first-issue list
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces a substantial new controller runtime/API layer (Hono + OpenAPI routes), runtime orchestration helpers for OpenClaw (health/sync/auth profiles/plugin/config writers), and adds/updates repo-level documentation and GitHub automation files.
Changes:
- Add a new
apps/controllerservice with runtime state tracking, health/sync loops, proxy-aware fetch, and multiple API route modules. - Add OpenClaw runtime writers (config, plugins, auth profiles, model state, credit-guard state) and supporting utilities (URL resolution, path helpers).
- Add/expand repository docs and automation (SECURITY/CONTRIBUTING/CODE_OF_CONDUCT/ARCHITECTURE, GitHub workflows/templates, editor settings).
Reviewed changes
Copilot reviewed 98 out of 1332 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/controller/src/runtime/state.ts | Adds runtime state types, initializer, and aggregate status recomputation. |
| apps/controller/src/runtime/runtime-health.ts | Adds gateway health probing via proxy-aware fetch. |
| apps/controller/src/runtime/openclaw-watch-trigger.ts | Adds filesystem “touch” triggers for config/skill watch reloads. |
| apps/controller/src/runtime/openclaw-runtime-plugin-writer.ts | Ensures runtime plugins are materialized into extensions dir. |
| apps/controller/src/runtime/openclaw-runtime-model-writer.ts | Writes authoritative runtime model state JSON. |
| apps/controller/src/runtime/openclaw-gateway-url.ts | Adds helpers to resolve HTTP/WS gateway URLs. |
| apps/controller/src/runtime/openclaw-config-writer.ts | Writes OpenClaw config, de-dupes writes, syncs weixin account index. |
| apps/controller/src/runtime/openclaw-auth-profiles-writer.ts | Writes/merges auth profiles for agent workspaces. |
| apps/controller/src/runtime/openclaw-auth-profiles-store.ts | Adds auth profiles store with serialized per-file update queue and OAuth connection state aggregation. |
| apps/controller/src/runtime/loops.ts | Adds sync/health/analytics background loops and status updates. |
| apps/controller/src/runtime/gateway-client.ts | Adds thin JSON client wrapper over proxy-aware fetch. |
| apps/controller/src/runtime/credit-guard-state-writer.ts | Writes desktop locale state for credit guard. |
| apps/controller/src/routes/workspace-template-routes.ts | Adds workspace template list/snapshot/upsert routes. |
| apps/controller/src/routes/user-routes.ts | Adds /me profile and auth-source routes. |
| apps/controller/src/routes/session-routes.ts | Adds internal session create/update and public session list/details/history/delete routes. |
| apps/controller/src/routes/runtime-config-routes.ts | Adds get/set runtime config endpoints. |
| apps/controller/src/routes/provider-oauth-routes.ts | Adds provider OAuth start/status/provider-status/disconnect endpoints. |
| apps/controller/src/routes/model-routes.ts | Adds model/provider registry/config/validate and quota fallback routes. |
| apps/controller/src/routes/integration-routes.ts | Adds integration list/connect/refresh/delete routes. |
| apps/controller/src/routes/desktop-routes.ts | Adds desktop internal routes including shell-open/ready/fallback events/preferences + compaction notify endpoint. |
| apps/controller/src/routes/desktop-rewards-routes.ts | Adds desktop rewards status/session/claim/set-balance routes. |
| apps/controller/src/routes/bot-routes.ts | Adds bot CRUD + pause/resume endpoints. |
| apps/controller/src/routes/artifact-routes.ts | Adds artifact CRUD/list/stats endpoints. |
| apps/controller/src/lib/v8-coverage.ts | Adds optional v8 coverage flush for desktop e2e. |
| apps/controller/src/lib/secrets.ts | Adds secret presence check + redaction helper. |
| apps/controller/src/lib/proxy-fetch.ts | Adds proxy-aware fetch with NO_PROXY merging, timeouts, and safe error redaction. |
| apps/controller/src/lib/provider-base-url.ts | Adds base URL normalization helper. |
| apps/controller/src/lib/path-utils.ts | Adds home expansion and relative child path validation helper. |
| apps/controller/src/lib/openclaw-config-serialization.ts | Adds deterministic OpenClaw config normalization/serialization. |
| apps/controller/src/lib/model-provider-runtime.ts | Adds runtime descriptors + model ref building/parsing helpers. |
| apps/controller/src/lib/managed-models.ts | Adds managed cloud model resolver + predicate. |
| apps/controller/src/lib/logger.ts | Adds structured JSON logger. |
| apps/controller/src/lib/channel-connect-error.ts | Adds typed ChannelConnectError helper. |
| apps/controller/src/index.ts | Adds controller entrypoint w/ graceful shutdown and optional coverage flush. |
| apps/controller/src/app/env.ts | Adds env loading/parsing with workspace-root .env support. |
| apps/controller/src/app/create-app.ts | Adds Hono app wiring, CORS, route registration, and /health endpoint. |
| apps/controller/src/app/container.ts | Adds DI container wiring for controller services and runtime subsystems. |
| apps/controller/src/app/bootstrap.ts | Adds controller bootstrap sequence and WS/run-loop wiring. |
| apps/controller/scripts/generate-openapi.ts | Adds script to generate OpenAPI doc JSON from app routes. |
| apps/controller/scripts/bundle-runtime-plugins.mjs | Adds script to bundle runtime plugins + dependencies into .dist-runtime. |
| apps/controller/package.json | Adds controller package scripts/deps. |
| apps/controller/.env.example | Adds controller env example + GitHub token note. |
| SECURITY.md | Adds security policy and reporting instructions (EN/zh). |
| LICENSE | Adds MIT license file. |
| CONTRIBUTING.md | Adds comprehensive contributing guide (EN) + links. |
| CODE_OF_CONDUCT.md | Adds Contributor Covenant CoC. |
| CLAUDE.md | Adds pointer to AGENTS instructions file. |
| ARCHITECTURE.md | Adds architecture overview doc. |
| .well-known/bothub-verify.txt | Adds verification token file. |
| .vscode/settings.json | Adds markdown paste image path setting. |
| .vscode/extensions.json | Recommends markdown paste image extension. |
| .vaunt/config.yml | Adds Vaunt achievements configuration. |
| .npmrc | Adds npm config (shamefully-hoist=true). |
| .nexu-dev/skills/nano-banana/scripts/file-upload.js | Adds Gemini Files API upload/list/get/delete helper script. |
| .github/workflows/nexu-pal-triage-command.yml | Adds workflow to process triage commands on issue comments. |
| .github/workflows/nexu-pal-needs-triage-notify.yml | Adds workflow to notify on needs-triage label. |
| .github/workflows/nexu-pal-issue-opened.yml | Adds workflow to welcome + auto-process new issues. |
| .github/workflows/github-metrics.yml | Adds daily GitHub metrics SVG generation workflow. |
| .github/workflows/feishu-pr-notify.yml | Adds Feishu notification for fork PR opens. |
| .github/workflows/feishu-issue-notify.yml | Adds Feishu notification for new issues. |
| .github/workflows/feishu-discussion-notify.yml | Adds Feishu notification for new discussions. |
| .github/workflows/docs-ci.yml | Adds docs build workflow with change detection. |
| .github/workflows/desktop-prepare-release.yml | Adds desktop release PR preparation workflow. |
| .github/workflows/desktop-ci-dist-lite.yml | Adds macOS dist-lite desktop CI workflow. |
| .github/workflows/desktop-ci-dev.yml | Adds desktop dev CI workflow (macOS + Windows). |
| .github/workflows/desktop-beta.yml | Adds scheduled/manual desktop beta build + publish pipeline. |
| .github/workflows/desktop-auto-tag.yml | Adds auto-tag + release creation on release PR merge. |
| .github/workflows/ci.yml | Adds CI workflow (typecheck/lint/test/build/lockfile/esm checks). |
| .github/workflows/cancel-closed-pr-desktop-e2e.yml | Adds workflow to cancel stale desktop e2e runs when PR closes. |
| .github/pull_request_template.md | Adds PR template. |
| .github/ISSUE_TEMPLATE/improvement.yml | Adds improvement issue template. |
| .github/ISSUE_TEMPLATE/feature_request.yml | Adds feature request issue template. |
| .github/ISSUE_TEMPLATE/config.yml | Configures issue templates + disables blank issues. |
| .github/ISSUE_TEMPLATE/bug_report.yml | Adds bug report template. |
| .github/CODEOWNERS | Adds CODEOWNERS entry. |
| .gitattributes | Adds default LF normalization + Windows script CRLF rules. |
| .dockerignore | Adds docker ignore patterns. |
| .agents/skills/process-pr-reviews/SKILL.md | Adds skill doc for processing PR reviews. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { serve } from "@hono/node-server"; | ||
| import { bootstrapController } from "./app/bootstrap.js"; | ||
| import { createContainer } from "./app/container.js"; | ||
| import { createApp } from "./app/create-app.js"; | ||
| import { logger } from "./lib/logger.js"; | ||
| import { flushV8CoverageIfEnabled } from "./lib/v8-coverage.js"; |
There was a problem hiding this comment.
The PR description is focused on three UI fixes (Windows update notification UI, welcome page language switcher, update banner layout) plus a README contribution-guide hint, but the diff primarily adds a large new controller service (runtime orchestration, API routes, scripts, workflows, and multiple repo governance docs). Please update the PR description to match the actual scope, or split this into smaller PRs aligned to the stated fixes to keep review/rollback risk manageable.
| const provider = | ||
| typeof typed.provider === "string" ? typed.provider : undefined; | ||
| const expiresAt = | ||
| typeof typed.expires === "number" ? typed.expires : undefined; | ||
| const providerId = | ||
| provider === undefined ? undefined : OAUTH_PROVIDER_ID_MAP[provider]; | ||
| if (!providerId || expiresAt === undefined || expiresAt <= Date.now()) { | ||
| continue; | ||
| } | ||
| connectedProviderIds.add(providerId); |
There was a problem hiding this comment.
As written, getOAuthConnectionState() only recognizes providers that exist in OAUTH_PROVIDER_ID_MAP. If typed.provider is already canonical (e.g. "openai"), providerId becomes undefined and the connection is ignored. Consider mapping via OAUTH_PROVIDER_ID_MAP[provider] ?? provider so unmapped providers still count, while preserving special-case remaps.
| import { readdirSync, rmSync } from "node:fs"; | ||
| import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; |
There was a problem hiding this comment.
This async sync path uses readdirSync/rmSync, which will block the Node.js event loop while scanning/deleting files. Since this executes during config writes (potentially frequent), prefer fs/promises.readdir + fs/promises.rm (or batch deletes) to avoid blocking controller request handling.
| for (const entry of readdirSync(accountsDir, { withFileTypes: true })) { | ||
| if (!entry.isFile() || !entry.name.endsWith(".json")) continue; | ||
| const id = entry.name.replace(/\.sync\.json$|\.json$/, ""); | ||
| if (!validIds.has(id)) { | ||
| rmSync(path.join(accountsDir, entry.name), { force: true }); | ||
| } | ||
| } |
There was a problem hiding this comment.
This async sync path uses readdirSync/rmSync, which will block the Node.js event loop while scanning/deleting files. Since this executes during config writes (potentially frequent), prefer fs/promises.readdir + fs/promises.rm (or batch deletes) to avoid blocking controller request handling.
| const configIdSet = new Set(accountIds); | ||
| const mergedIds = [ | ||
| ...new Set([ | ||
| ...existingIds.filter((id) => configIdSet.has(id)), | ||
| ...accountIds, | ||
| ]), | ||
| ].filter((id) => !id.startsWith(NEXU_INTERNAL_ACCOUNT_PREFIX)); | ||
|
|
||
| // Only write if changed | ||
| if (JSON.stringify(mergedIds) === JSON.stringify(existingIds)) { | ||
| return; | ||
| } | ||
|
|
||
| await mkdir(indexDir, { recursive: true }); | ||
| await writeFile(indexPath, JSON.stringify(mergedIds, null, 2), "utf8"); | ||
|
|
||
| // Remove orphan credential/sync files for accounts no longer in the | ||
| // authoritative set. This prevents listStoredWeixinAccountIds() from | ||
| // resurrecting stale accounts that were removed from config. | ||
| const accountsDir = path.join(indexDir, "accounts"); | ||
| try { | ||
| const validIds = new Set(mergedIds); |
There was a problem hiding this comment.
The change detection compares mergedIds vs existingIds via JSON.stringify, which is sensitive to ordering and also compares against the unfiltered existingIds (so removing internal IDs will always look like a change). If the goal is: (a) avoid unnecessary writes and (b) keep a stable canonical file, consider normalizing both sides (e.g., filter out internal IDs and sort) before comparing, or compare as sets and write a deterministic sorted list.
| const configIdSet = new Set(accountIds); | |
| const mergedIds = [ | |
| ...new Set([ | |
| ...existingIds.filter((id) => configIdSet.has(id)), | |
| ...accountIds, | |
| ]), | |
| ].filter((id) => !id.startsWith(NEXU_INTERNAL_ACCOUNT_PREFIX)); | |
| // Only write if changed | |
| if (JSON.stringify(mergedIds) === JSON.stringify(existingIds)) { | |
| return; | |
| } | |
| await mkdir(indexDir, { recursive: true }); | |
| await writeFile(indexPath, JSON.stringify(mergedIds, null, 2), "utf8"); | |
| // Remove orphan credential/sync files for accounts no longer in the | |
| // authoritative set. This prevents listStoredWeixinAccountIds() from | |
| // resurrecting stale accounts that were removed from config. | |
| const accountsDir = path.join(indexDir, "accounts"); | |
| try { | |
| const validIds = new Set(mergedIds); | |
| const normalizeAccountIds = (ids: string[]): string[] => | |
| [...new Set(ids.filter((id) => !id.startsWith(NEXU_INTERNAL_ACCOUNT_PREFIX)))].sort(); | |
| const configIdSet = new Set(accountIds); | |
| const mergedIds = [ | |
| ...new Set([ | |
| ...existingIds.filter((id) => configIdSet.has(id)), | |
| ...accountIds, | |
| ]), | |
| ]; | |
| const normalizedExistingIds = normalizeAccountIds(existingIds); | |
| const normalizedMergedIds = normalizeAccountIds(mergedIds); | |
| // Only write if changed | |
| if ( | |
| JSON.stringify(normalizedMergedIds) === | |
| JSON.stringify(normalizedExistingIds) | |
| ) { | |
| return; | |
| } | |
| await mkdir(indexDir, { recursive: true }); | |
| await writeFile(indexPath, JSON.stringify(normalizedMergedIds, null, 2), "utf8"); | |
| // Remove orphan credential/sync files for accounts no longer in the | |
| // authoritative set. This prevents listStoredWeixinAccountIds() from | |
| // resurrecting stale accounts that were removed from config. | |
| const accountsDir = path.join(indexDir, "accounts"); | |
| try { | |
| const validIds = new Set(normalizedMergedIds); |
| const outputPath = new URL("../openapi.json", import.meta.url).pathname; | ||
| fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2)); |
There was a problem hiding this comment.
Using new URL(...).pathname can produce incorrect paths on Windows (leading /, URL decoding issues). Prefer converting the file URL to a real filesystem path (e.g., via fileURLToPath) before writing, so generate-openapi works cross-platform.
| return null; | ||
| } | ||
|
|
||
| export function mergeNoProxyEntries( |
There was a problem hiding this comment.
The new proxy/NO_PROXY matching + timeout/abort behavior is non-trivial and security/ops sensitive. Add unit tests covering: (1) mergeNoProxyEntries dedupe + loopback injection, (2) shouldBypassProxy hostname matching edge cases (leading dot, wildcard, IPv6 bracket form), and (3) proxyFetch timeout vs caller-abort error shaping and message redaction.
| ); | ||
| } | ||
|
|
||
| export function shouldBypassProxy( |
There was a problem hiding this comment.
The new proxy/NO_PROXY matching + timeout/abort behavior is non-trivial and security/ops sensitive. Add unit tests covering: (1) mergeNoProxyEntries dedupe + loopback injection, (2) shouldBypassProxy hostname matching edge cases (leading dot, wildcard, IPv6 bracket form), and (3) proxyFetch timeout vs caller-abort error shaping and message redaction.
| return safeError; | ||
| } | ||
|
|
||
| export async function proxyFetch( |
There was a problem hiding this comment.
The new proxy/NO_PROXY matching + timeout/abort behavior is non-trivial and security/ops sensitive. Add unit tests covering: (1) mergeNoProxyEntries dedupe + loopback injection, (2) shouldBypassProxy hostname matching edge cases (leading dot, wildcard, IPv6 bracket form), and (3) proxyFetch timeout vs caller-abort error shaping and message redaction.
| } catch { | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
Errors from the watch-trigger are silently swallowed. If the touch fails (permissions, missing parent dir, invalid path), OpenClaw watchers may not reload and this will be hard to diagnose. Consider logging at least a debug/warn message with a redacted path + error code so operational issues can be triaged.
| } catch { | |
| return; | |
| } | |
| } | |
| } catch (error) { | |
| console.warn( | |
| `[OpenClawWatchTrigger] Failed to touch watch-trigger file "${this.redactPath(filePath)}"${ | |
| this.getErrorCode(error) ? ` (code: ${this.getErrorCode(error)})` : "" | |
| }`, | |
| ); | |
| return; | |
| } | |
| } | |
| private redactPath(filePath: string): string { | |
| return path.basename(filePath); | |
| } | |
| private getErrorCode(error: unknown): string | undefined { | |
| if ( | |
| typeof error === "object" && | |
| error !== null && | |
| "code" in error && | |
| typeof error.code === "string" | |
| ) { | |
| return error.code; | |
| } | |
| return undefined; | |
| } |
本次PR完成了三项任务修复和贡献指南的文档改进:
影响范围: