Skip to content

fix: ensure gateway restarts on install failure via ERR trap - #2152

Open
Zhe-SH-CN wants to merge 26 commits into
MemTensor:mainfrom
Zhe-SH-CN:fix/install-trap-gateway-restart
Open

fix: ensure gateway restarts on install failure via ERR trap#2152
Zhe-SH-CN wants to merge 26 commits into
MemTensor:mainfrom
Zhe-SH-CN:fix/install-trap-gateway-restart

Conversation

@Zhe-SH-CN

Copy link
Copy Markdown

Description

When set -euo pipefail causes the install script to exit early (e.g. npm install fails, tarball extraction fails), the OpenClaw gateway was already stopped but never restarted, leaving the service down.

This PR adds an ERR trap after gateway stop in both install scripts (memos-local-plugin/install.sh and memos-local-openclaw/install.sh) that restarts the gateway on failure. The trap is cleared once the normal gateway start step succeeds.

Related Issue: Fixes #1391

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested

  • bash -n syntax check passes for both scripts
  • Manual code review confirming trap lifecycle (set after stop, cleared after start)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have linked the issue to this PR

Zyntrael and others added 17 commits July 8, 2026 16:13
* feat(api): align OpenMem v1 SDK with cloud API
Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com>
…#2113)

Rewrite scheduler Quick Start examples to call the real /product/scheduler REST endpoints with requests instead of non-existent MemOSClient methods; align response fields with the actual handler output; rename the mis-named ' wait.md' to 'wait.md'.

Closes MemTensor#2083

Co-authored-by: sunqi <sunqi@memtensor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ensor#2112)

fix(api): show structured add example in /docs (messages not string)

Swagger UI rendered APIADDRequest.messages as "string" because it picks the
leading `str` branch of the `str | MessageList | RawMessageList` union when
generating the example. Add a model-level json_schema_extra example so /docs
shows a copy-paste-ready payload with a structured messages list.

Schema-only change: field types, validation and runtime behaviour are
unchanged, so the core add pipeline is unaffected.

Closes MemTensor#1505

Co-authored-by: sunqi <sunqi@memtensor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…emTensor#2096)

The hermes adapter's __init__.py imported MemosHttpClient from
bridge_client, but the class was never committed — every plugin python
test failed at collection and any real Hermes host loading memos_provider
hit ImportError at module load. The phantom usages arrived via the
pr/may27-fixes merge (0398e0e) as a half-committed HTTP-bridge feature.

Revert the four usages and the HTTP-first branches in initialize /
_reconnect_bridge, remove the _connect_http_bridge helper and the now-
unused probe_viewer_status / startup_lock_active imports (their
definitions in daemon_manager.py are untouched for a future HTTP PR).
Add test_module_imports_cleanly regression test asserting
MemTensorProvider / MemosBridgeClient / BridgeError are present on the
real contract surfaces and MemosHttpClient is absent on both
memos_provider and bridge_client, so this class of import-level
breakage cannot silently return.

Restores the adapter to stdio-only behavior. Net diff: 57 additions /
54 deletions across the adapter plus the regression test.

Fixes MemTensor#2096
Avoid re-running dictConfig on every get_logger call, since repeated configuration can close and replace active handlers while background threads are emitting records. Keep logging configuration process-local and reconfigure only after a PID change.
Use FastAPI lifespan cleanup to stop scheduler and RabbitMQ resources before logging handlers are torn down during worker or pod shutdown.
## Description

fix: configure logging once per process
fix: close scheduler resources on API shutdown

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- [x] Unit Test - tests/api/test_lifecycle.py, tests/test_log.py

## Checklist

- [x] I have performed a self-review of my own code | 我已自行检查了自己的代码
- [x] I have commented my code in hard-to-understand areas |
我已在难以理解的地方对代码进行了注释
- [x] I have added tests that prove my fix is effective or that my
feature works | 我已添加测试以证明我的修复有效或功能正常
- [ ] I have created related documentation issue/PR in
[MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) (if applicable) |
我已在 [MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) 中创建了相关的文档
issue/PR(如果适用)
- [x] I have linked the issue to this PR (if applicable) | 我已将 issue
链接到此 PR(如果适用)
- [x] I have mentioned the person who will review this PR | 我已提及将审查此 PR
的人

## Reviewer Checklist
- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] Made sure Checks passed
- [ ] Tests have been provided
@Memtensor-AI Memtensor-AI added area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Jul 23, 2026
@Memtensor-AI
Memtensor-AI requested review from hijzy and whipser030 July 23, 2026 10:36
harvey_xiang and others added 4 commits July 23, 2026 19:07
* Fix MemTensor#1540: fix: (MemTensor#1824)

docs(memos-local-plugin): clarify install path and stale dir names (MemTensor#1540)

The README's 'Quick start' section told users to use install.sh instead
of npm install, but the warning was buried and users still tried
'npm install -g @memtensor/memos-local-plugin' first. The reporter in
MemTensor#1540 encountered this on a Hermes deployment.

This change:

- Promotes the 'do not run npm install -g' notice to a prominent
  IMPORTANT callout explaining why global install is wrong (no
  agent-home deploy, no config.yaml, no bridge/viewer) and that the
  tarball intentionally ships built artifacts only.
- Adds a Troubleshooting subsection covering the two specific symptoms
  in the bug report: the 'package not found' misread, and the stale
  web/ and site/ directory names (web/ is now viewer/, site/ was
  removed by commit 26e7e3d).
- Mentions install.ps1 for Windows alongside install.sh.
- CHANGELOG: record the docs fix and reference MemTensor#1540.

Documentation-only change; no code or runtime behavior touched.

Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* Fix MemTensor#1888: [Bug] test_system_parser.py: SystemParser.__init__() got an unexpected keyword a (MemTensor#1889)

fix: remove invalid chunker parameter from SystemParser test instantiation

- SystemParser.__init__() signature changed to (embedder, llm=None)
- Test was still passing chunker=None causing TypeError
- Fixes all 5 failing tests in test_system_parser.py

Fixes MemTensor#1888

Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* Fix MemTensor#1525: [Bug] clean_json_response crashes with cryptic AttributeError when given None (MemTensor#1884)

* test: add comprehensive tests for clean_json_response (issue MemTensor#1525)

- Add test suite in tests/mem_os/test_format_utils.py
- Cover None input ValueError with diagnostic message
- Cover markdown removal, whitespace stripping, edge cases
- Verify fix for AttributeError when LLM returns None

* style: format clean_json_response tests

---------

Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* Fix MemTensor#1901: share_cube_with_user passes swapped args to _validate_cube_access — fails for ev (MemTensor#1903)

fix: validate current user not target in share_cube_with_user (MemTensor#1901)

share_cube_with_user(cube_id, target_user_id) called
_validate_cube_access(cube_id, target_user_id), but the validator
signature is (user_id, cube_id). The cube_id therefore landed in the
user_id slot and _validate_user_exists raised
"User '<cube_id>' does not exist or is inactive" for every well-formed
call, making the API unusable.

The in-code comment "Validate current user has access to this cube"
already documented the correct intent: the sharing user (self.user_id)
must have access to the cube being shared, not the target. Switch the
call to self._validate_cube_access(self.user_id, cube_id). The target
user's existence is independently checked on the next line via
validate_user(target_user_id), so that path is unchanged.

Add regression tests in tests/mem_os/test_memos_core.py that pin down:
- validate_user_cube_access is consulted with (self.user_id, cube_id),
- add_user_to_cube is called with (target_user_id, cube_id) on success,
- a missing target raises "Target user '<id>' does not exist".

Closes MemTensor#1901

Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* feat(memos): chunk batch reflection scoring

* Fix MemTensor#1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid  (MemTensor#1899)

* Fix MemTensor#1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors

Issue MemTensor#1897 reported ~12,900 paid LLM requests in 24 h on Hermes
against a DeepSeek key with insufficient balance. The local
`system_model_status` row count (12,900) closely tracked the
provider-side `request_count` (11,344) for the same billing window.
The naming is misleading: `system_model_status` is not a health probe;
it is the audit row written once per LLM call (ok / fallback / error)
inside `core/llm/client.ts`. With no circuit breaker, every pipeline
subscriber (capture / session-relation / reward / L2 / L3 / skill /
retrieval LLM filter / world-model) kept firing on every turn / closed
episode / induction, generating one paid request each.

Add a per-`LlmClient` circuit breaker:

- Trips on terminal errors: HTTP 401/402/403 or messages containing
  `insufficient balance` / `invalid api key` / `unauthorized` /
  `account suspended` / `billing`.
- Open: short-circuits subsequent calls inside the facade without
  contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with
  `details.circuitOpen=true` so existing catch blocks still work.
- Half-open after cool-down (default 5 min, configurable, min 30 s):
  next call probes the provider; success closes the breaker, terminal
  failure re-opens it for another cool-down.
- Host fallback rescues a call without tripping the breaker —
  fallback exists precisely to keep going when the primary is down.
- Coalesces `system_model_status="circuit_open"` audit rows to at
  most one per ~25 s while the breaker stays open, so we don't
  replace paid spam with audit-row spam.
- Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason`
  via `LlmClientStats` for the Overview viewer card.
- Enabled by default; legacy behaviour available via
  `circuitBreaker.enabled = false`.

Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts`
covering trip on 402, trip on "insufficient balance" message, no
trip on generic transient, coalescing, half-open close on success,
host-fallback rescues without trip, disabled mode, stats fields,
and re-open on terminal probe failure. All 59 LLM and 28 pipeline
tests pass; `tsc --noEmit` clean.

Out of scope (tracked separately): 429 `Retry-After` handling
(issue MemTensor#1620), per-tool rate limits, daily budget caps.

* Fix LLM breaker with host fallback

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>
…nsor#2145)

This reverts commit 21a27ed.

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Fixes MemTensor#1391

When  causes the install script to exit early
(e.g. npm install fails, tarball extraction fails, etc.), the
OpenClaw gateway was already stopped but never restarted, leaving
the service down.

Add an ERR trap after  in both install scripts that
restarts the gateway on failure. The trap is cleared once the
normal  step succeeds.
@Zhe-SH-CN
Zhe-SH-CN force-pushed the fix/install-trap-gateway-restart branch from 7ee646c to 90c53de Compare July 23, 2026 11:07
@Zhe-SH-CN
Zhe-SH-CN changed the base branch from main to dev-v2.0.24 July 23, 2026 11:07
@Memtensor-AI Memtensor-AI added area:api 云服务 / FastAPI / OpenAPI / MCP area:core MOS 编排层 / 框架底座 / 跨模块问题 labels Jul 23, 2026
@Memtensor-AI Memtensor-AI added the area:docs 文档、示例 label Jul 23, 2026
@Memtensor-AI

Memtensor-AI commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2152
Task: 8d2f1b1cb54362c9
Base: main
Head: fix/install-trap-gateway-restart

🔍 OpenCodeReview found 4 issue(s) in this PR.


1. apps/memos-local-openclaw/install.sh (L232-L234)

The cleanup_on_exit function references ${OPENCLAW_BIN} directly, but OPENCLAW_BIN is not passed or validated inside the function. If the trap fires in a rare scenario where OPENCLAW_BIN is empty or unset (e.g., early exit before it is assigned), the command degenerates to gateway start 2>&1, which will fail silently in an unexpected way.

The sibling file apps/memos-local-plugin/install.sh uses a dedicated GATEWAY_RECOVERY_BIN variable that is explicitly set to the binary path only after the gateway is confirmed stopped, and is checked with -n before executing. The same guard should be applied here:

if [[ "${GATEWAY_RECOVERY_STATE:-inactive}" == "needs_recovery" && -n "${OPENCLAW_BIN:-}" ]]; then
💡 Suggested Change

Before:

  if [[ "${GATEWAY_RECOVERY_STATE:-inactive}" == "needs_recovery" ]]; then
    local recovery_out=""
    if ! recovery_out="$("${OPENCLAW_BIN}" gateway start 2>&1)"; then

After:

  if [[ "${GATEWAY_RECOVERY_STATE:-inactive}" == "needs_recovery" && -n "${OPENCLAW_BIN:-}" ]]; then
    local recovery_out=""
    if ! recovery_out="$("${OPENCLAW_BIN}" gateway start 2>&1)"; then

2. apps/memos-local-openclaw/install.sh (L339)

GATEWAY_RECOVERY_STATE is set to "needs_recovery" immediately after gateway stop, but there is no sleep or wait to confirm the gateway has actually stopped before continuing. The sibling script apps/memos-local-plugin/install.sh adds a sleep 1 after stopping. Without any delay, the subsequent lsof port-check and reinstallation may race against a still-running gateway process, leading to port conflicts or file-lock issues.

💡 Suggested Change

Before:

GATEWAY_RECOVERY_STATE="needs_recovery"

After:

GATEWAY_RECOVERY_STATE="needs_recovery"
sleep 1

3. apps/memos-local-openclaw/install.sh (L356)

TMP_PACK_DIR is initialized to "" at the top of the script, but is only populated inside the if [[ -f "${PLUGIN_VERSION}" ]] / else block later. If mktemp -d fails (e.g., disk full), TMP_PACK_DIR will be empty and the cleanup guard [[ -n "${TMP_PACK_DIR:-}" ]] correctly skips removal — that part is fine. However, if the early exit 1 branches between lines 415-418 are taken (dependency failure), TMP_PACK_DIR may already be populated and the EXIT trap will correctly clean it up. This is actually correct behavior, but it is worth confirming that every code path that populates TMP_PACK_DIR is covered by the trap, since the old per-trap placement on the same line was removed.

💡 Suggested Change

Before:

TMP_PACK_DIR="$(mktemp -d)"

After:

TMP_PACK_DIR="$(mktemp -d)" || { error "Failed to create temp directory"; exit 1; }

4. apps/memos-local-plugin/install.sh (L660-L668)

The foreground gateway fallback path (nohup + wait_for_viewer) does not reset GATEWAY_RECOVERY_STATE to "inactive" before returning. If the viewer becomes reachable via the nohup fallback and the function returns at line 667, the EXIT trap will still see GATEWAY_RECOVERY_STATE="needs_recovery" and attempt an additional gateway start against an already-running process.

The same GATEWAY_RECOVERY_STATE="inactive" / GATEWAY_RECOVERY_BIN="" pair that guards the success path at lines 647-648 should be applied here before each early return in the fallback block.

💡 Suggested Change

Before:

  nohup "${oc_bin}" gateway >/tmp/openclaw-memos-gateway.log 2>&1 &
  sleep 2
  if wait_for_viewer "${OPENCLAW_PORT}"; then
    echo
    success "OpenClaw install complete"
    printf "       ${DIM}Plugin:${NC}    %s\n" "${HOME}/.openclaw/extensions/${PLUGIN_ID}"
    printf "       ${DIM}Viewer:${NC}    ${CYAN}http://127.0.0.1:${OPENCLAW_PORT}/${NC}\n"
    return 0
  fi

After:

  nohup "${oc_bin}" gateway >/tmp/openclaw-memos-gateway.log 2>&1 &
  sleep 2
  if wait_for_viewer "${OPENCLAW_PORT}"; then
    echo
    success "OpenClaw install complete"
    printf "       ${DIM}Plugin:${NC}    %s\n" "${HOME}/.openclaw/extensions/${PLUGIN_ID}"
    printf "       ${DIM}Viewer:${NC}    ${CYAN}http://127.0.0.1:${OPENCLAW_PORT}/${NC}\n"
    GATEWAY_RECOVERY_STATE="inactive"
    GATEWAY_RECOVERY_BIN=""
    return 0
  fi

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: INCONCLUSIVE

Automated tests inconclusive (auto-generated test defect); treated as non-blocking. Manual review recommended. Details: Test failures are unrelated to the diff, which only modifies install.sh scripts. The failures are pre-existing test defects: one has an incorrect status assertion, and another references a non-existent 'role' column in the traces table.

Branch: fix/install-trap-gateway-restart

@syzsunshine219
syzsunshine219 changed the base branch from dev-v2.0.24 to main July 23, 2026 13:55

@WeiminLee WeiminLee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I need to change my previous approval. This PR is not ready to merge in its current state.

Blocking findings:

  1. The branch is currently conflicting against main. gh reports mergeable=CONFLICTING, and merge-tree shows conflicts in unrelated files such as apps/memos-local-plugin/adapters/hermes/memos_provider/__init__.py, pyproject.toml, src/memos/__init__.py, src/memos/api/client.py, src/memos/log.py, tests/api/test_client.py, and tests/test_log.py.

  2. The PR is not scoped to the stated install failure fix. It includes API client/model changes, local-plugin LLM changes, lifecycle/logging changes, scheduler docs, version bumps, and large API tests. Please rebase onto current main and reduce this PR to the gateway restart change only, ideally just the affected install scripts and focused tests/manual validation notes.

  3. The current install-script fix uses an ERR trap after stopping the gateway, but the normal gateway start path is not explicitly checked before clearing the trap. If the final start command fails, the recovery behavior is ambiguous and may attempt another start via the ERR trap. Please handle the final start explicitly and clear recovery state only after a successful restart.

Please update the branch and rerun CI after narrowing the diff.

Copilot AI lite review requested due to automatic review settings August 6, 2026 13:22
@Memtensor-AI Memtensor-AI removed the area:core MOS 编排层 / 框架底座 / 跨模块问题 label Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@Memtensor-AI Memtensor-AI removed area:api 云服务 / FastAPI / OpenAPI / MCP area:docs 文档、示例 labels Aug 6, 2026
@syzsunshine219

Copy link
Copy Markdown
Collaborator

Updated the original branch with merge commit 2e07638f:

  • Merged current main and resolved all 8 unrelated conflicts in favor of main.
  • Narrowed the final PR diff from 22 files to 3 files: the two installers plus a focused regression test.
  • Replaced the ERR traps with one composable EXIT cleanup path, so temporary-directory cleanup is preserved.
  • Recovery is armed only after gateway stop; failures before the normal restart recover the gateway once.
  • The final gateway start is checked explicitly. A failed final start exits non-zero and is not retried by the exit trap; recovery state is cleared only after success.
  • Added fault-injection coverage for broken tar extraction, failed npm pack, and failed final gateway start.

Validation:

  • bash -n passed for both installers.
  • Focused tests: 3/3 passed.
  • Full local-plugin suite: 1305 passed, 2 skipped across 160 files.
  • npm run lint passed.
  • npm run build passed.
  • Final diff and whitespace checks passed.

@syzsunshine219

Copy link
Copy Markdown
Collaborator

Follow-up pushed in 2d302f60 to address the latest OCR feedback:

  • warnings/errors now consistently use stderr in the legacy installer
  • the legacy installer reports success only after gateway start succeeds
  • recovery state now distinguishes an install-time failure from a confirmed final-start failure
  • a confirmed final-start failure is intentionally not retried from the EXIT cleanup, matching the human review request to avoid a second ambiguous start attempt
  • regression coverage now checks one final start attempt, nonzero exit, failure output, and no false success output

Validation: 160 test files passed (1305 tests passed, 2 skipped), targeted recovery tests 3/3, lint, build, both bash -n checks, and git diff --check.

@syzsunshine219

Copy link
Copy Markdown
Collaborator

OCR follow-up in 2b37f109:

  • The reported foreground-fallback double start was a false positive: GATEWAY_RECOVERY_BIN is cleared unconditionally before the viewer wait/fallback path. I added an explicit ordering comment and a regression test that exercises this path; the observed calls are exactly gateway stop, gateway start, then the foreground gateway command, with no EXIT-trap restart.
  • The recovery variables intentionally remain global because the EXIT trap must read them after install_openclaw returns. The top-level dispatch invokes install_openclaw at most once; this constraint and trap lifetime are now documented together.

Validation remains green: targeted recovery tests 4/4; full suite 160 files, 1306 passed and 2 skipped; lint, build, shell syntax, and diff checks passed.

@syzsunshine219

Copy link
Copy Markdown
Collaborator

OCR follow-up pushed in b3fc2167:

Addressed:

  • both installers now retain and print the original gateway start output when EXIT recovery fails
  • recovery control is a single explicit state (inactive, needs_recovery, final_failed) instead of two booleans
  • tests assert that recovery diagnostics include the gateway error

Not applicable:

  • the legacy installer already disables recovery on the first command after a successful final start; separate INT/TERM traps do not remove the shell-level gap between two commands
  • a failed final start returns from install_openclaw; it cannot fall through into the later foreground fallback block. The fallback test also confirms no second service start occurs

Validation: targeted tests 4/4; full suite 160 files, 1306 passed and 2 skipped; lint, build, both shell syntax checks, and diff checks passed.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: INCONCLUSIVE

Automated tests inconclusive (flaky/non-deterministic test); treated as non-blocking. Manual review recommended. Details: The failing test asserts an old task's status is 'completed' or 'skipped' after a session change, but the status was 'active'. This test is unrelated to the diff, which only touches install.sh shell scripts and a new plugin test file. [advisory, non-gating] AI-generated tests on branch test/auto-gen-8d2f1b1cb54362c9-20260806222544: 47/51 passed, 4 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: fix/install-trap-gateway-restart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: