Skip to content

同步本地 main 到远端 - #2

Merged
xiangking merged 44 commits into
mainfrom
sync-main
Sep 27, 2026
Merged

xiangking merged 44 commits into
mainfrom
sync-main

Conversation

@xiangking

Copy link
Copy Markdown
Owner

同步本地 main 分支的 43 个提交到远端 main。已通过前端测试、后端测试、TypeScript 类型检查和生产构建。

xiangking and others added 30 commits September 2, 2026 02:41
Add the same _BACKEND_ROOT/sys.path bootstrap used by test_paths.py,
test_asr_adapter.py and test_rpc_bridge.py to:
- test_video_call_state.py
- test_memory_dreamer.py
- test_storage_migration.py

Refs: #1
Widen the here-local:// extension allow-list to include bmp/mov/avi/mkv so
character_assets, character_image, and character_animation pickers no longer
offer formats the serving layer rejects. Extract the allow-list into a shared
module and derive the character picker filters from it, with a regression test
asserting every picker extension is accepted.
Skip non-dict character entries and non-dict sprite entries, and treat a
None/non-list sprites field as empty instead of crashing with
AttributeError/TypeError. Legal here configurations keep the same
migration behavior and idempotency.
Bring persona video call, sys.path test bootstrap, and malformed characters/sprites migration defense onto main. Resolve the only content conflict in src/main/main.ts by keeping PR #2 isLocalAssetExtension whitelist together with the token LRU.
Honor the public timezone parameter (default Asia/Shanghai) for both
the next-midnight wake and MemoryDreamer .dream-date markers, reusing
core.timezone ZoneInfo resolution with tzdata fallbacks.
Same ZoneInfo datetime subtraction ignores offset changes, so the
scheduler now uses timestamps and regression-tests New York spring
and fall transitions.
…/types.ts

The Electron-native chat stack never ran: runtime chat and legacy import
already go through the Python sidecar (`chat` / `import_legacy` RPC). The
four modules only referenced each other and were built by tsconfig alone.

- Delete src/main/chat-service.ts, src/main/legacy-importer.ts,
  src/main/config-store.ts and src/shared/defaults.ts.
- Drop the now-orphaned legacy models from src/shared/types.ts (AppConfig,
  ApiSettings, SystemSettings, ProactiveSettings, Character, SpriteSource,
  Emotion, ChatMessage, ChatRequest, ChatEvent, AppState); keep
  Attachment, LegacyImportReport and HereDesktopApi untouched.
- Drop the `yaml` dependency, used only by the removed legacy importer.
- Prune dist-electron before build so electron-builder cannot package JS
  left behind for removed sources.

Refs #6
…rs from main.ts

Issue #7 phase 1 (staged, renderer-first). Pure mechanical extraction, no
behavior change: moved code is copied verbatim, only signature params/imports
added.

- src/renderer/dom.ts (new): $, $$, esc, clone, call-pixel icons,
  refreshIcons, updateCallControl, switchRow, field, pathField, selectField,
  editableChoiceField, pageHeader.
- src/renderer/settings-html.ts (new): adapter label maps, schemaFields,
  delivery/capability helpers, profile helpers, renderPlatformSettings,
  renderCharacterSettings, renderSpriteSettings (+ editor/slot helpers),
  renderMemorySettings, renderStorageSettings. DesktopState is now an explicit
  parameter instead of a module-level closure.
- src/renderer/main.ts: 3016 -> 2503 lines; keeps startup/assembly, DOM writes,
  event binding and chat/audio/ASR/sprite runtime (stateful orchestration stays
  centralized; deferred to a later phase).
Keep protocol I/O, startup order, dispatch, and patchable names in
rpc_bridge.py. Move method bodies into bridge/ helpers that resolve
event/ASR/migration symbols through late-bound hooks so existing
patch.object(rpc_bridge, ...) tests stay valid.
python rpc_bridge.py registers the sidecar as __main__, so hooks that
only looked up sys.modules['rpc_bridge'] crashed on BACKEND_ROOT before
ready. Alias the running module before importing bridge, accept either
name at lookup time, and cover the real script entry with a subprocess
ready/ping JSON regression.
sprite_prefix is user-editable and was joined into shutil.rmtree without
checking ownership. Require a single safe path segment, refuse absolute
paths, traversal, the storage root, outbound symlinks, and shared prefixes,
and skip file deletion when the prefix is unsafe so unrelated files stay.
Ensures consistent UTF-8 encoding for Python subprocess I/O on all platforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves GHSA-55q2-fjhq-7xh7 (moderate severity XSS in DOMPurify <=3.4.12)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update vitest from 4.0.0 to 4.1.11 to get latest fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
修复 Issue #9,防止角色删除时误删资产根以外目录。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… paths

After storage migration, reload_runtime() was reloading config but keeping
the old MemoryStore instance with the stale root path. This caused new
memories to be written to the old directory even after migration succeeded.

Fixed by recreating self.memory = AgentMemoryStore() after config.reload()
so it picks up the updated paths.memory_dir from the freshly reloaded config.

Fixes #10

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously, _save_single_config used open(file,'w') which truncates the
file immediately, then yaml.dump. If dump failed (disk full, serialization
error), the original config was already lost and the error was only printed,
not raised.

Now using atomic write-and-replace pattern:
1. Serialize to temp file in same directory (ensures same filesystem)
2. If successful, atomically replace original with os.replace()
3. If any stage fails, clean up temp file and raise RuntimeError
4. Original file is never touched until write succeeds

Added comprehensive tests covering:
- yaml.dump failure preserves original
- write I/O error preserves original
- successful save works correctly
- no temp file leaks on any failure path

All 55 backend tests pass (51 existing + 4 new).

Fixes #11

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rename_character compared Path strings, so on case-insensitive
filesystems (macOS) 'here' vs 'HERE' looked like different folders.
The merge copied the home into itself and then shutil.rmtree removed
it, destroying MEMORY.md/USER.md/SOUL.md and all other data.

Detect the same directory via os.path.samefile (inode) and treat
same-path renames as no-ops. Genuine renames and merges are unchanged.

Tests: backend.tests.test_memory_rename + RPC save_config regression.
import_legacy copied the legacy config verbatim, including
storage_paths.yaml. When the old install used custom absolute or
relative memory/asset folders, Electron kept writing back into them
while only the fixed memory/characters subdirectories were copied.

Read the legacy storage layout, copy the real memory/asset folders (and
the other data dirs) into the Electron app home, copy config without the
legacy storage_paths.yaml, then rewrite absolute character asset paths
onto the Electron folders. Failures never adopt the legacy layout.

Also resolve paths through symlinked roots when rewriting migrated
paths (macOS /var vs /private/var).

Tests: subprocess legacy import probe (relative + absolute custom
storage) plus symlinked-root migration regression.
import_character_state_assets deleted the existing state folder before
decoding the new GIF/WebP, importing OpenCV, extracting video frames or
copying files. Any later failure left the previous sprite destroyed while
the config still referenced it.

Generate all frames in a sibling staging folder, then swap it in while
keeping the old folder as a backup. Restore the backup when the config
save or sprite update fails, and clean up staging on any error, so a
failed replacement never removes working assets.

Tests: corrupt GIF, undecodable video, copy failure, config-save
failure, and source-file-inside-target replacement.
OpenCV reports frame write failures (for example a full disk) by
returning False instead of raising. The staging code skipped the frame
and still committed the incomplete folder, deleting the previous
assets. Treat any False return as an error so the rollback keeps the old
sprites and config.

Test injects a two-frame video where the first frame writes and the
second returns False, and asserts the previous bytes, in-memory config
and on-disk config are unchanged with no staging leftovers.
import_legacy copied legacy assets into the Electron asset folder but
only rewrote absolute paths under the old asset root. Characters that
referenced assets with the supported relative forms (characters/... or
defaults/characters/...) kept those strings, so with a custom Electron
asset folder they resolved against the Electron root and pointed at
missing files.

Resolve each character asset field with the legacy AppPaths layout
(instead of the process working directory) and map it onto the Electron
asset root. Covers sprite path, frames, spritesheet_path and
visual_reference_image.

Tests: unit coverage for relative references and untouched external
paths, plus an end-to-end import with a custom Electron asset folder
and relative legacy references.
The sprite_prefix guard proved a folder belongs to the character but not
that a file is unused. Deleting one sprite reference could therefore
remove a file another character or another sprite still pointed at, and
replacing a state folder could clobber shared frames.

Add reference tracking on top of the ownership guard: collect every
resolved asset path (path, frames, spritesheet_path, voice_path) held by
surviving sprites, then skip physical deletion of any file that is still
referenced, any directory that still contains a referenced file, and
refuse a state-folder replacement that would destroy one. Wired into the
RPC delete_character_sprite / import_character_state_assets and the
CharacterManager delete_character / delete_all_sprites /
delete_single_sprite entry points.
…rences

Two review findings on the shared-reference guard:

- CharacterManager collected references with the raw path string, so
  supported characters/... references were resolved against the process
  working directory instead of the app layout. Whole-character deletion
  through HereBackend.delete_character could therefore miss a surviving
  reference and remove the file. Use resolve_character_asset_path with the
  configured AppPaths, matching the RPC/runtime behaviour.
- Reference collection only scanned sprites and ignored the character-level
  visual_reference_image, which is a persisted and consumed asset. Include
  it for every surviving character, keep it when only a character's sprites
  are cleared, and retarget it in the same commit when a replaced state
  folder held it (restoring it on rollback).
install_dependencies announced busy=true before running pip, but only
the success path published busy=false. A non-zero exit, a 30 minute
timeout, or a subprocess start failure left the renderer's global busy
bar on until the backend restarted.

Wrap the install in try/except/finally so every path publishes exactly
one matching busy=false, with the completion text on success and a
readable error message (failure tail, timeout detail, or OSError text)
otherwise. Non-zero exits keep the last 12 stdout lines as before.
The README only covered backend:setup/backend:setup:full and described
backend:runtime as always installing Vosk/PyAudio, which is wrong on
Linux because requirements-runtime.txt gates pyaudio/vosk on
macOS/Windows.

- Distinguish development installs, the settings-page on-demand install
  (RPC install_dependencies) and the release runtime build.
- Describe backend:runtime accurately (uv, standalone CPython, macOS
  Homebrew PortAudio bundling/re-signing, prune and RPC smoke test).
- Add a platform table for pyaudio/vosk in the release runtime.
- Document the network, writable data dir and post-install re-check or
  restart requirements, plus troubleshooting for failed installs, missing
  system audio libraries and still-missing dependencies.
setup-full only installs requirements-asr.txt and
requirements-hermes.txt; neither contains opencv-python, so importing
video sprites after setup:full can still fail on a missing cv2. State
that video is on-demand only: it is absent from setup:full and from the
release runtime requirements, and is installed from the settings page or
manually into backend/.venv.
@xiangking
xiangking merged commit 59a8b50 into main Sep 27, 2026
2 checks passed
@xiangking
xiangking deleted the sync-main branch September 27, 2026 14:03
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.

1 participant