Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hugagent-desktop",
"version": "0.2.15",
"version": "0.2.16",
"private": true,
"description": "HugAgentOS 桌面客户端(远程连接 + Windows/macOS/Linux 离线本机服务)",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hugagent-desktop"
version = "0.2.15"
version = "0.2.16"
description = "HugAgentOS桌面客户端"
edition = "2021"
rust-version = "1.77"
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "HugAgentOS",
"version": "0.2.15",
"version": "0.2.16",
"identifier": "com.hugagent.desktop",
"build": {
"frontendDist": "../../src/frontend/dist",
Expand Down
31 changes: 25 additions & 6 deletions src/backend/api/routes/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ class ImportRequest(BaseModel):
# ── Helpers ───────────────────────────────────────────────────────────────────


def _invalidate_model_caches() -> None:
"""Single invalidation point for model-config changes: the resolve cache plus
every runtime singleton built from it.

The mem0 singleton captures the memory/embedding config at init time; clearing
the resolve cache alone leaves memory running on stale credentials (a first
init against the placeholder key means every later write fails 401). The reset
is lazy — the next memory read/write rebuilds against the fresh config — so it
is cheap enough to apply on every change without filtering by role.
"""
ModelConfigService.get_instance().invalidate_cache()
try:
from core.memory import service as memory_service

memory_service.reset_runtime()
except Exception as exc: # a failed runtime reset must not block saving the config
logger.warning("[models] memory runtime reset failed (ignored): %s", exc)


def _mask_api_key(key: str) -> str:
if not key or len(key) <= 8:
return "****"
Expand Down Expand Up @@ -489,7 +508,7 @@ async def create_provider_endpoint(
currency=body.currency,
display_name=body.display_name,
)
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data=_provider_to_dict(provider, _get_pricing(db, provider.model_name)))


Expand Down Expand Up @@ -547,7 +566,7 @@ async def update_provider_endpoint(
display_name=provider.display_name,
**price_fields,
)
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data=_provider_to_dict(provider, _get_pricing(db, provider.model_name)))


Expand All @@ -566,7 +585,7 @@ async def delete_provider_endpoint(
)
if not delete_provider(db, provider_id):
raise HTTPException(status_code=404, detail="Provider not found")
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data={"deleted": provider_id})


Expand Down Expand Up @@ -652,7 +671,7 @@ async def assign_role_endpoint(

if not assign_role(db, role_key, body.provider_id):
raise HTTPException(status_code=400, detail="Assignment failed")
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data={"role_key": role_key, "provider_id": body.provider_id})


Expand All @@ -666,7 +685,7 @@ async def unassign_role_endpoint(
if role_key not in ROLE_DEFINITIONS:
raise HTTPException(status_code=404, detail=f"Unknown role: {role_key}")
unassign_role(db, role_key)
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data={"role_key": role_key, "provider_id": None})


Expand Down Expand Up @@ -715,5 +734,5 @@ async def import_endpoint(
):
"""导入模型配置(供应商 + 角色分配)。仅限管理员(CONFIG_TOKEN / can_system_config,同 export);overwrite=True 时覆盖同名条目,导入后刷新模型配置缓存。"""
result = import_all(db, body.model_dump(), overwrite=body.overwrite)
ModelConfigService.get_instance().invalidate_cache()
_invalidate_model_caches()
return success_response(data=result)
14 changes: 13 additions & 1 deletion src/backend/core/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
MessageFeedback,
)
from core.db.models.config import ModelProvider, ModelRoleAssignment, SystemConfig
from core.db.models.evolution import (
EvolutionAgentProfile,
EvolutionCandidate,
EvolutionCreditDecision,
EvolutionEpisode,
EvolutionEvaluation,
EvolutionEvidencePack,
EvolutionMemoryOp,
EvolutionPromotionLink,
EvolutionRelease,
EvolutionTraceEvent,
)
from core.db.models.identity import (
ChannelConnection,
DingTalkConnection,
Expand All @@ -46,7 +58,7 @@
)
from core.db.models.knowledge import CatalogOverride, KBChunk, KBDocument, KBSpace
from core.db.models.logs import SkillCallLog, SubAgentCallLog, ToolCallLog
from core.db.models.memory import MemorySanitizerRule, ProfileMemory
from core.db.models.memory import MemoryRefShadow, MemorySanitizerRule, ProfileMemory
from core.db.models.ontology import (
OntologyDraft,
OntologyEnforcementEvent,
Expand Down
27 changes: 19 additions & 8 deletions src/backend/core/memory/audit.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
"""记忆审计旁路 —— 社区版 no-op stub。

记忆审计(存 hash 不存原文、链路追溯)属商业版合规能力;社区版保持
同名接口但不落任何审计数据,调用方(L1 画像 / 抽取写入器)零改动。
"""Memory audit side-channel — community-edition no-op stub.

Memory auditing (hash-only storage, trace chains) is a commercial-edition
compliance capability; the community edition keeps the same module surface but
records nothing, so callers (the L1 profile writer, the extraction pipeline)
need zero changes.

The stubs must stay *call-compatible* with the EE implementation, whose real
signatures are ``record(ctx, action, layer, *, memory_id=None, content=None,
reason=None)`` etc. — the first three are positional. Narrowing the stub
signature makes positional calls raise TypeError, and that raise lands *after*
the business transaction has committed, turning a successful write into a
reported failure (bitten in the 0.2.15 desktop local build: the L1 preference
was persisted, yet the UI never showed the write card). Hence ``*args`` /
``**kwargs`` catch-alls.
"""

from __future__ import annotations

from typing import Any, Iterable
from typing import Any


async def record(ctx: Any = None, **kwargs: Any) -> None:
async def record(*args: Any, **kwargs: Any) -> None:
return None


async def record_batch(ctx: Any = None, items: Iterable[Any] | None = None, **kwargs: Any) -> None:
async def record_batch(*args: Any, **kwargs: Any) -> None:
return None


def record_sync(ctx: Any = None, **kwargs: Any) -> None:
def record_sync(*args: Any, **kwargs: Any) -> None:
return None


Expand Down
Loading
Loading