Skip to content

feat: 完成首次贡献指南及三项任务修复#1

Closed
masher-pp wants to merge 678 commits into
mainfrom
feat/first-contribution-guide
Closed

feat: 完成首次贡献指南及三项任务修复#1
masher-pp wants to merge 678 commits into
mainfrom
feat/first-contribution-guide

Conversation

@masher-pp
Copy link
Copy Markdown
Owner

本次PR完成了三项任务修复和贡献指南的文档改进:

  1. fix: 版本更新通知设计杂乱 #1005 - 优化了Windows版本更新通知的UI设计
  2. fix: 欢迎页语言切换器无效 #448 - 修复了语言切换器的race condition问题
  3. fix: 更新通知横幅遮挡侧边栏 #859 - 调整了更新横幅位置避免遮挡
  4. docs: 在README中添加贡献指南提示 - 引导用户查看贡献指南

影响范围:

  • Desktop app (Electron shell)
  • Web dashboard (React UI)
  • Documentation

PerishCode and others added 30 commits March 22, 2026 15:34
* 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
…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
Celina-create and others added 26 commits April 10, 2026 21:13
- 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
@masher-pp masher-pp requested a review from Copilot April 16, 2026 12:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/controller service 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.

Comment on lines +1 to +6
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";
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +169
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);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
import { readdirSync, rmSync } from "node:fs";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +74
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 });
}
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +67
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);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +13
const outputPath = new URL("../openapi.json", import.meta.url).pathname;
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return null;
}

export function mergeNoProxyEntries(
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
);
}

export function shouldBypassProxy(
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return safeError;
}

export async function proxyFetch(
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +24
} catch {
return;
}
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
} 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;
}

Copilot uses AI. Check for mistakes.
@masher-pp masher-pp closed this Apr 16, 2026
@masher-pp masher-pp deleted the feat/first-contribution-guide branch April 16, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.