From 136d8bb102b2657a06eac3317d49a5ae950705ab Mon Sep 17 00:00:00 2001
From: Luhaozhu
Date: Fri, 23 Oct 2026 23:14:04 -0700
Subject: [PATCH] Sync CE terminal client, local projects, search and sidebar
updates
---
.../references/api-envelope.md | 6 +-
.../references/architecture.md | 265 +-
.../references/error-codes.md | 2 +
.../scripts/scaffold_feature.sh | 145 +-
.../templates/extension.py | 38 +
.../hugagent-backend-dev/templates/model.py | 78 +-
.../templates/repository.py | 70 +-
.../hugagent-backend-dev/templates/route.py | 156 +-
.../hugagent-backend-dev/templates/service.py | 138 +-
.../hugagent-backend-dev/templates/test.py | 171 +-
.../assets/design-v2/icons-ui/attachment.svg | 3 +
.../assets/design-v2/icons-ui/automation.svg | 3 +
.../assets/design-v2/icons-ui/check.svg | 3 +
.../design-v2/icons-ui/chevron-right.svg | 3 +
.../assets/design-v2/icons-ui/close.svg | 3 +
.../assets/design-v2/icons-ui/knowledge.svg | 3 +
.../assets/design-v2/icons-ui/my-space.svg | 3 +
.../assets/design-v2/icons-ui/new-chat.svg | 3 +
.../assets/design-v2/icons-ui/search.svg | 3 +
.../assets/design-v2/icons-ui/send.svg | 3 +
.../assets/design-v2/icons-ui/settings.svg | 3 +
.../assets/design-v2/icons-ui/skill.svg | 3 +
.../assets/design-v2/logo/favicon.svg | 5 +
.../design-v2/logo/hugagentos-glyph-black.svg | 4 +
.../design-v2/logo/hugagentos-glyph-blue.svg | 5 +
.../design-v2/logo/hugagentos-glyph-white.svg | 4 +
.../logo/hugagentos-mark-mono-black.svg | 5 +
.../logo/hugagentos-mark-mono-white.svg | 5 +
.../logo/hugagentos-mark-reverse.svg | 6 +
.../design-v2/logo/hugagentos-mark-small.svg | 5 +
.../assets/design-v2/logo/hugagentos-mark.svg | 6 +
.../assets/design-v2/tokens/appTheme.v2.ts | 39 +
.../design-v2/tokens/hugagentos-tokens.v2.css | 123 +
.../tokens/hugagentos-tokens.v2.json | 223 +
.../design-v2/tokens/motionTokens.v2.ts | 32 +
.../references/css-variables.md | 277 +-
.../references/icon-assets.md | 46 +-
.../references/sse-events.md | 294 +-
.../references/ui-design-spec.md | 667 ++-
.../scripts/scaffold_component.sh | 5 +-
.../templates/component.tsx | 115 +-
.../templates/css-module.css | 114 +-
.../hugagent-frontend-dev/templates/hook.ts | 98 +-
.../hugagent-frontend-dev/templates/store.ts | 121 +-
.env.example | 11 +-
.gitignore | 10 +-
Makefile | 6 +-
README.md | 363 +-
README_CN.md | 290 +-
...uirements-desktop-linux-aarch64-py311.lock | 50 +-
...quirements-desktop-linux-x86_64-py311.lock | 58 +-
...uirements-desktop-macos-aarch64-py311.lock | 58 +-
...quirements-desktop-macos-x86_64-py311.lock | 58 +-
.../requirements-desktop-windows-py311.lock | 60 +-
desktop/requirements-desktop.txt | 11 +-
desktop/scripts/build-runtime.mjs | 3 +
desktop/scripts/desktop-dependencies.test.mjs | 33 +-
desktop/scripts/test-native-tools.py | 2 +-
docker-compose.yml | 19 +-
...Dockerfile.opensandbox-server.dockerignore | 16 -
docker/Dockerfile.personal-agent-test | 6 -
.../compose.application-hosting.yml | 4 +-
.../Dockerfile.object-storage | 0
.../compose.object-storage.yml | 10 +-
.../init-object-storage.py | 0
.../Dockerfile.opensandbox-server | 11 +-
...Dockerfile.opensandbox-server.dockerignore | 18 +
.../opensandbox-config.toml.tpl | 0
.../opensandbox_capacity.py | 31 +
.../opensandbox_expirations.py | 0
.../opensandbox_mount_recovery.py | 22 +-
.../opensandbox_mount_worker.py | 0
.../opensandbox_object_mounts.py | 0
.../patch_opensandbox_server.py | 11 +
.../render-opensandbox-config.py | 0
.../Dockerfile.personal-agent | 8 +-
.../compose.personal-agent.yml | 4 +-
.../desktop}/helpers.rc | 0
.../desktop}/mimeapps.list | 0
.../desktop}/pa-browser | 0
.../desktop}/pa-browser-helper.desktop | 0
.../desktop}/pa-browser.desktop | 0
.../desktop}/pa-stream.py | 0
.../hugagent-cli-entrypoint.sh | 0
.../personal-agent-entrypoint.sh | 4 +-
.../render-personal-agent-config.py | 0
docker/{ => sandbox}/Dockerfile.opensandbox | 27 +-
docker/{ => sandbox}/Dockerfile.script-runner | 8 +-
docker/{ => sandbox}/apt-skills.txt | 0
docker/{ => sandbox}/package-skills.json | 0
.../requirements-script-runner.txt | 0
docker/{ => sandbox}/requirements-skills.txt | 0
docker/sandbox/sandbox-init.c | 78 +
.../site-template/init-react-site.sh | 0
.../site-template/react-vite/.npmrc | 0
.../site-template/react-vite/index.html | 0
.../react-vite/package-lock.json | 0
.../site-template/react-vite/package.json | 0
.../site-template/react-vite/src/App.jsx | 0
.../site-template/react-vite/src/index.css | 0
.../react-vite/src/lib/siteApi.js | 0
.../site-template/react-vite/src/main.jsx | 0
.../react-vite/src/pages/Home.jsx | 0
.../site-template/react-vite/vite.config.mjs | 0
.../{ => sandbox}/verify-browser-runtime.py | 0
document/README.md | 173 +-
document/en/README.md | 129 +-
document/en/api/overview.md | 8 +-
document/en/architecture/backend.md | 18 +-
document/en/architecture/data-model.md | 6 +-
document/en/architecture/overview.md | 111 +-
document/en/deployment/README.md | 4 -
.../deployment/artifact-storage-key-repair.md | 55 -
document/en/deployment/desktop-local-mode.md | 2 +-
document/en/deployment/docker-compose.md | 16 +-
.../en/deployment/environment-variables.md | 23 +-
document/en/deployment/offline-production.md | 84 -
document/en/deployment/quick-install.md | 34 +-
document/en/deployment/space-files.md | 18 +-
document/en/deployment/uos1070-desktop.md | 101 -
document/en/development/backend.md | 16 +-
document/en/development/container-layout.md | 28 -
document/en/development/frontend.md | 9 +
.../en/development/workbench-migration.md | 29 +-
document/en/development/workbench-routing.md | 2 +-
document/en/editions/build-ce.md | 12 +-
document/en/getting-started/introduction.md | 170 +-
document/en/getting-started/quick-start.md | 121 +-
document/en/modules/admin-console.md | 16 +-
document/en/modules/agent-skills.md | 38 +-
document/en/modules/application-hosting.md | 32 +-
document/en/modules/auth.md | 10 +-
document/en/modules/automation.md | 50 +-
document/en/modules/autonomous-loop.md | 6 +-
document/en/modules/browser-automation.md | 2 +-
document/en/modules/canvas-artifacts.md | 14 +-
document/en/modules/catalog.md | 21 +-
document/en/modules/chat.md | 58 +-
document/en/modules/knowledge-base-wiki.md | 14 +-
document/en/modules/knowledge-base.md | 44 +-
document/en/modules/mcp-tools.md | 50 +-
document/en/modules/memory.md | 8 +-
document/en/modules/model-providers.md | 28 +-
document/en/modules/personal-agent.md | 12 +-
document/en/modules/projects-myspace.md | 31 +-
document/en/modules/prompts.md | 31 +-
document/en/modules/sandbox.md | 58 +-
document/en/modules/sites.md | 14 +-
document/en/modules/storage.md | 4 +-
document/en/modules/workflow-jobs.md | 8 -
document/zh-CN/README.md | 132 +-
document/zh-CN/api/overview.md | 8 +-
document/zh-CN/architecture/backend.md | 18 +-
document/zh-CN/architecture/data-model.md | 6 +-
document/zh-CN/architecture/overview.md | 99 +-
document/zh-CN/deployment/README.md | 4 -
.../deployment/artifact-storage-key-repair.md | 48 -
.../zh-CN/deployment/desktop-local-mode.md | 3 +-
document/zh-CN/deployment/docker-compose.md | 20 +-
.../zh-CN/deployment/environment-variables.md | 23 +-
.../zh-CN/deployment/offline-production.md | 84 -
document/zh-CN/deployment/quick-install.md | 32 +-
document/zh-CN/deployment/space-files.md | 28 +-
document/zh-CN/deployment/uos1070-desktop.md | 87 -
document/zh-CN/development/backend.md | 16 +-
.../zh-CN/development/container-layout.md | 24 -
document/zh-CN/development/frontend.md | 7 +
.../zh-CN/development/workbench-migration.md | 29 +-
.../zh-CN/development/workbench-routing.md | 2 +-
document/zh-CN/editions/build-ce.md | 12 +-
.../zh-CN/getting-started/introduction.md | 159 +-
document/zh-CN/getting-started/quick-start.md | 110 +-
document/zh-CN/modules/admin-console.md | 16 +-
document/zh-CN/modules/agent-skills.md | 38 +-
document/zh-CN/modules/application-hosting.md | 32 +-
document/zh-CN/modules/auth.md | 10 +-
document/zh-CN/modules/automation.md | 46 +-
document/zh-CN/modules/autonomous-loop.md | 6 +-
document/zh-CN/modules/browser-automation.md | 2 +-
document/zh-CN/modules/canvas-artifacts.md | 14 +-
document/zh-CN/modules/catalog.md | 21 +-
document/zh-CN/modules/chat.md | 58 +-
document/zh-CN/modules/knowledge-base-wiki.md | 14 +-
document/zh-CN/modules/knowledge-base.md | 44 +-
document/zh-CN/modules/mcp-tools.md | 51 +-
document/zh-CN/modules/memory.md | 8 +-
document/zh-CN/modules/model-providers.md | 28 +-
document/zh-CN/modules/personal-agent.md | 12 +-
document/zh-CN/modules/projects-myspace.md | 29 +-
document/zh-CN/modules/prompts.md | 31 +-
document/zh-CN/modules/sandbox.md | 49 +-
document/zh-CN/modules/sites.md | 14 +-
document/zh-CN/modules/storage.md | 4 +-
document/zh-CN/modules/workflow-jobs.md | 8 -
examples/customer-chat/.env.example | 6 -
examples/customer-chat/.gitignore | 8 -
examples/customer-chat/README.md | 37 -
examples/customer-chat/debug.html | 1 -
examples/customer-chat/index.html | 9 -
examples/customer-chat/package-lock.json | 1224 -----
examples/customer-chat/package.json | 41 -
examples/customer-chat/preview.html | 1 -
.../customer-chat/scripts/build-component.mjs | 14 -
.../customer-chat/scripts/developer-proxy.mjs | 43 -
.../customer-chat/scripts/test-package.mjs | 30 -
examples/customer-chat/scripts/test-proxy.mjs | 26 -
examples/customer-chat/session.html | 1 -
examples/customer-chat/src/controls.ts | 76 -
examples/customer-chat/src/css.d.ts | 1 -
examples/customer-chat/src/customization.tsx | 29 -
examples/customer-chat/src/debug.ts | 32 -
examples/customer-chat/src/developer.css | 3 -
examples/customer-chat/src/login.ts | 11 -
examples/customer-chat/src/preview.tsx | 5 -
examples/customer-chat/src/session.ts | 20 -
examples/customer-chat/src/standalone.css | 6 -
examples/customer-chat/src/standalone.ts | 142 -
examples/customer-chat/src/theme.css | 19 -
examples/customer-chat/vite.config.mjs | 30 -
install.sh | 4 +-
packages/cli/.gitignore | 2 +
packages/cli/DESIGN.md | 218 +
packages/cli/README.md | 126 +
packages/cli/package-lock.json | 179 +
packages/cli/package.json | 30 +
packages/cli/src/config.ts | 112 +
packages/cli/src/engine/base.ts | 201 +
packages/cli/src/engine/engine.ts | 123 +
packages/cli/src/engine/errors.ts | 47 +
packages/cli/src/engine/headless.ts | 199 +
packages/cli/src/engine/http.ts | 303 ++
packages/cli/src/engine/index.ts | 33 +
packages/cli/src/engine/local.ts | 192 +
packages/cli/src/engine/remote.ts | 211 +
packages/cli/src/engine/server-command.ts | 56 +
packages/cli/src/main.ts | 318 ++
packages/cli/src/modes/interactive.ts | 37 +
packages/cli/src/modes/json.ts | 29 +
packages/cli/src/modes/print.ts | 75 +
packages/cli/src/modes/rpc.ts | 191 +
packages/cli/src/tui/app-editor.ts | 43 +
packages/cli/src/tui/app.ts | 855 +++
packages/cli/src/tui/bash.ts | 89 +
packages/cli/src/tui/clipboard.ts | 19 +
packages/cli/src/tui/commands.ts | 140 +
packages/cli/src/tui/events.ts | 108 +
packages/cli/src/tui/footer.ts | 51 +
packages/cli/src/tui/format.ts | 109 +
packages/cli/src/tui/plan.ts | 23 +
packages/cli/src/tui/prompts/confirm.ts | 45 +
packages/cli/src/tui/prompts/question.ts | 149 +
packages/cli/src/tui/prompts/select.ts | 48 +
packages/cli/src/tui/theme.ts | 51 +
packages/cli/src/tui/transcript.ts | 702 +++
packages/cli/src/version.ts | 5 +
packages/cli/test/cli-modes.test.mjs | 194 +
packages/cli/test/engine-fake-engine.mjs | 25 +
packages/cli/test/engine-fake-server.mjs | 204 +
packages/cli/test/engine-http.test.mjs | 121 +
packages/cli/test/engine-local.test.mjs | 93 +
packages/cli/test/tui-app.test.mjs | 303 ++
packages/cli/test/tui-transcript.test.mjs | 254 +
.../cli}/tsconfig.json | 19 +-
pyproject.toml | 26 +-
requirements.txt | 17 +-
scripts/build-hugagent-cli.py | 2 +-
scripts/performance/benchmark_preview.py | 2 +-
scripts/performance/serve_fixture.py | 6 +-
scripts/refactor/__init__.py | 1 +
scripts/refactor/check_ce_paths.py | 79 +
scripts/refactor/check_line_budget.py | 37 +
scripts/refactor/check_test_baseline.py | 75 +
scripts/refactor/import_contract.py | 209 +
scripts/refactor/rewrite_imports.py | 357 ++
scripts/refactor/snapshot_routes.py | 35 +
scripts/refactor/snapshot_schema.py | 81 +
scripts/refactor/tests/test_edition_seams.py | 33 +
scripts/refactor/tests/test_guards.py | 127 +
.../refactor/tests/test_package_markers.py | 20 +
.../refactor/tests/test_private_imports.py | 41 +
.../refactor/tests/test_rewrite_imports.py | 142 +
.../refactor/tests/test_snapshot_schema.py | 33 +
.../refactor/tests/test_supplemental_moves.py | 47 +
scripts/refactor/tests/test_test_baseline.py | 22 +
scripts/refactor/tests/test_test_moves.py | 45 +
scripts/repair_artifact_storage_keys.py | 4 +-
scripts/sync-workbench-schema.mjs | 4 +-
scripts/test-workbench-build.mjs | 2 +-
src/backend/alembic/env.py | 4 +
.../alembic/versions/ce_0001_initial.py | 2 +-
.../versions/ce_0002_reconcile_schema.py | 2 +-
.../versions/ce_0003_mcp_marketplace.py | 2 +-
.../versions/ce_0009_desktop_delivery.py | 2 +-
.../versions/ce_0010_desktop_channel_relay.py | 2 +-
.../alembic/versions/ce_0012_model_steps.py | 3 +-
.../alembic/versions/ce_0013_tool_media.py | 2 +-
.../versions/ce_0014_site_access_password.py | 2 +-
.../versions/ce_0019_history_display.py | 2 +-
.../versions/ce_0020_plugin_resources.py | 2 +-
.../versions/ce_0021_retire_site_storage.py | 2 +-
.../versions/ce_0028_personal_agent.py | 4 +-
.../ce_0031_automation_lease_token.py | 19 +
.../versions/ce_0032_automation_queue.py | 25 +
.../versions/ce_0033_automation_targets.py | 17 +
src/backend/api/__init__.py | 19 +-
src/backend/api/app.py | 28 +-
src/backend/api/deps.py | 201 +-
src/backend/api/edition.py | 13 +
src/backend/api/health.py | 11 +-
src/backend/api/lifecycle.py | 18 +-
src/backend/api/middleware/agent_api_scope.py | 11 +-
src/backend/api/middleware/logging.py | 2 +-
src/backend/api/openapi.py | 2 +-
.../v1/agents.py => personal_agents.py} | 28 +-
src/backend/api/routes/__init__.py | 15 +-
src/backend/api/routes/file_archive.py | 26 +-
src/backend/api/routes/file_location.py | 2 +-
src/backend/api/routes/files.py | 56 +-
src/backend/api/routes/v1/__init__.py | 53 +-
src/backend/api/routes/v1/agent_api_keys.py | 30 +-
src/backend/api/routes/v1/agent_responses.py | 54 +-
src/backend/api/routes/v1/api_keys.py | 12 +-
src/backend/api/routes/v1/artifacts.py | 108 +-
src/backend/api/routes/v1/audio.py | 15 +-
src/backend/api/routes/v1/auth.py | 58 +-
src/backend/api/routes/v1/catalog.py | 37 +-
.../api/routes/v1/catalog_mutations.py | 12 +-
src/backend/api/routes/v1/chat_forks.py | 2 +-
src/backend/api/routes/v1/chat_modes.py | 2 +-
src/backend/api/routes/v1/chat_runs.py | 17 +-
src/backend/api/routes/v1/chats/README.md | 4 +-
src/backend/api/routes/v1/chats/__init__.py | 2 +
src/backend/api/routes/v1/chats/compaction.py | 69 +
src/backend/api/routes/v1/chats/confirms.py | 27 +-
src/backend/api/routes/v1/chats/feedback.py | 14 +-
src/backend/api/routes/v1/chats/history.py | 16 +-
.../api/routes/v1/chats/pending_access.py | 8 +-
src/backend/api/routes/v1/chats/questions.py | 28 +-
src/backend/api/routes/v1/chats/reruns.py | 111 +-
src/backend/api/routes/v1/chats/run_views.py | 40 +-
src/backend/api/routes/v1/chats/sessions.py | 20 +-
src/backend/api/routes/v1/chats/sidebar.py | 12 +-
src/backend/api/routes/v1/classify.py | 2 +-
src/backend/api/routes/v1/commands.py | 11 +-
src/backend/api/routes/v1/content.py | 11 +-
.../api/routes/v1/desktop_capability.py | 824 ---
src/backend/api/routes/v1/evolution.py | 29 +-
src/backend/api/routes/v1/file_parse.py | 3 +-
src/backend/api/routes/v1/file_text.py | 2 +-
src/backend/api/routes/v1/file_upload.py | 22 +-
src/backend/api/routes/v1/internal_jobs.py | 15 +-
.../api/routes/v1/job_callback_session.py | 2 +-
src/backend/api/routes/v1/jobs.py | 4 +-
src/backend/api/routes/v1/kb.py | 873 ----
src/backend/api/routes/v1/loops.py | 81 +-
src/backend/api/routes/v1/me_capabilities.py | 70 +-
src/backend/api/routes/v1/me_skill_files.py | 44 +-
src/backend/api/routes/v1/me_system.py | 12 +-
src/backend/api/routes/v1/memories.py | 81 +-
src/backend/api/routes/v1/mock_sso.py | 31 +-
src/backend/api/routes/v1/model_reasoning.py | 10 +-
src/backend/api/routes/v1/models.py | 176 +-
src/backend/api/routes/v1/myspace_folders.py | 4 +-
src/backend/api/routes/v1/ontologies.py | 20 +-
src/backend/api/routes/v1/plans.py | 199 +-
src/backend/api/routes/v1/plugin_resources.py | 98 +-
src/backend/api/routes/v1/plugins.py | 47 +-
src/backend/api/routes/v1/project_api_keys.py | 30 +-
.../api/routes/v1/project_environments.py | 8 +-
src/backend/api/routes/v1/projects.py | 69 +-
.../api/routes/v1/prompt_management.py | 95 +-
src/backend/api/routes/v1/public_chats.py | 23 +-
src/backend/api/routes/v1/space_files.py | 22 +-
src/backend/api/routes/v1/space_previews.py | 11 +-
src/backend/api/routes/v1/summary.py | 2 +-
src/backend/api/routes/v1/tool_approval.py | 10 +-
src/backend/api/routes/v1/users.py | 16 +-
src/backend/api/startup/bootstrap.py | 72 +-
src/backend/api/startup/commands.py | 8 +-
src/backend/api/startup/memory.py | 10 +-
src/backend/api/startup/plugins.py | 45 +-
src/backend/api/startup/runtime.py | 87 +-
src/backend/api/startup/workers.py | 63 +-
.../content => api/transports}/__init__.py | 0
src/backend/api/transports/run_sse.py | 85 +
src/backend/cli.py | 539 +-
src/backend/core/agent_runtime/README.md | 18 +
.../core/agent_runtime/README.zh-CN.md | 18 +
.../resources => agent_runtime}/__init__.py | 0
src/backend/core/agent_runtime/budget.py | 74 +
src/backend/core/agent_runtime/direct.py | 458 ++
.../core/agent_runtime/direct_state.py | 46 +
src/backend/core/agent_runtime/events.py | 71 +
src/backend/core/agent_runtime/host.py | 11 +
.../agent_runtime/integrations}/__init__.py | 0
.../integrations}/compaction_stream.py | 8 +-
.../agent_runtime/integrations/evolution.py | 160 +
.../core/agent_runtime/integrations/memory.py | 304 ++
.../integrations/memory_budget.py | 34 +
.../integrations/memory_identity.py | 74 +
.../integrations/memory_session.py | 178 +
.../integrations/memory_state.py | 23 +
.../integrations/ontology_evidence.py | 229 +
.../integrations/ontology_repair.py | 366 ++
src/backend/core/agent_runtime/main_state.py | 45 +
.../agent_runtime}/plugin_visibility.py | 14 +-
.../core/agent_runtime/prepare_direct.py | 126 +
.../core/agent_runtime/prepare_main.py | 289 +
src/backend/core/agent_runtime/runner.py | 444 ++
src/backend/core/agent_runtime/skills.py | 176 +
.../agent_runtime}/strategy.py | 0
.../core/agent_runtime/stream_direct.py | 303 ++
src/backend/core/agent_runtime/stream_main.py | 359 ++
src/backend/core/agent_runtime/targets.py | 208 +
.../core/agent_skills/backends/__init__.py | 14 -
.../core/agent_skills/cache_refresh.py | 51 -
src/backend/core/auth/__init__.py | 2 +-
.../agent_api.py} | 2 +-
src/backend/core/auth/agent_api_scope.py | 2 +-
.../core/{services => auth}/api_key_runs.py | 33 +-
.../api_key_service.py => auth/api_keys.py} | 12 +-
src/backend/core/auth/backend.py | 275 +-
src/backend/core/auth/bearer.py | 149 +
src/backend/core/auth/capabilities.py | 4 +-
src/backend/core/auth/edition.py | 5 +
src/backend/core/auth/local_user_bootstrap.py | 83 +
src/backend/core/auth/local_user_policy.py | 16 +
.../local_users.py} | 155 +-
.../core/auth/local_users_constants.py | 40 +
src/backend/core/auth/mock_ticket_store.py | 26 +-
src/backend/core/auth/model_selection.py | 52 +
src/backend/core/auth/oa_ticket_store.py | 23 +-
...t_policy.py => personal_account_policy.py} | 0
...count_view.py => personal_account_view.py} | 0
...es.py => personal_edition_capabilities.py} | 0
...iface.py => personal_permissions_iface.py} | 2 +-
src/backend/core/auth/project_api_scope.py | 2 +-
src/backend/core/auth/public_chat.py | 6 +-
src/backend/core/auth/public_identity.py | 36 +
src/backend/core/auth/session.py | 36 +-
.../user_service.py => auth/users.py} | 11 +-
src/backend/core/capabilities/__init__.py | 17 +-
.../core/capabilities/agents/__init__.py | 1 +
.../agents/available_resources.py | 273 +
src/backend/core/capabilities/agents/base.py | 435 ++
.../capabilities/agents/base_constants.py | 33 +
.../agents/builtin.py} | 4 +-
.../agents/edition.py} | 2 +-
.../agents/markdown.py} | 0
.../capabilities/agents/version_history.py | 180 +
src/backend/core/capabilities/archive.py | 18 +-
.../core/capabilities/catalog/__init__.py | 1 +
.../catalog/api.py} | 14 +-
.../catalog/common.py} | 4 +-
.../catalog/details.py} | 20 +-
.../catalog/loader.py} | 57 +-
.../catalog/migration.py} | 4 +-
.../catalog/overrides.py} | 0
.../catalog/resolver.py} | 78 +-
.../catalog/runtime.py} | 53 +-
.../core/capabilities/identity_rules.py | 36 +
.../manifest_protocol.py} | 3 +-
src/backend/core/capabilities/mcp/__init__.py | 1 +
.../mcp/builtin_specs.py} | 4 -
.../mcp/edition.py} | 0
.../mcp/management.py} | 64 +-
src/backend/core/capabilities/mcp/oauth.py | 385 ++
.../core/capabilities/mcp/oauth_storage.py | 124 +
.../mcp/service.py} | 60 +-
.../core/capabilities/mcp/tool_metadata.py | 13 +
.../core/capabilities/mcp/tool_schema.py | 20 +
.../capabilities/mcp/tool_schema_backfill.py | 120 +
.../core/capabilities/plugins/README.md | 23 +
.../{ => capabilities}/plugins/__init__.py | 0
.../{ => capabilities}/plugins/connections.py | 2 +-
.../plugins/management/__init__.py | 43 +
.../plugins/management/admin_config.py | 18 +-
.../plugins/management/bundle_upgrade.py} | 8 +-
.../plugins/management/cloud.py} | 52 +-
.../plugins/management/components.py | 32 +-
.../plugins/management/contract.py} | 0
.../plugins/management/defaults.py | 2 +-
.../plugins/management/details.py | 76 +-
.../plugins/management/errors.py} | 0
.../plugins/management/installation.py | 72 +-
.../plugins/management/lifecycle.py | 42 +-
.../plugins/management/package.py} | 10 +-
.../plugins/management/packages.py | 33 +-
.../plugins/management/projection.py | 28 +-
.../plugins/management/queries.py | 50 +-
.../plugins/packaging/__init__.py | 0
.../plugins/packaging/archive.py | 4 +-
.../plugins/packaging/definitions.py | 4 +-
.../plugins/packaging/importer.py | 24 +-
.../plugins/packaging/models.py | 0
.../plugins/packaging/presentation.py | 31 +
.../plugins/packaging/runtime_assets.py | 0
.../plugins/packaging/skill_transform.py | 2 +-
.../plugins/packaging/sources.py | 31 +-
.../plugins/resources/__init__.py | 1 +
.../plugins/resources/artifacts.py | 4 +-
.../plugins/resources/authentication.py | 4 +-
.../plugins/resources/configuration.py | 0
.../plugins/resources/confinement.py | 7 +-
.../plugins/resources/crypto.py | 0
.../plugins/resources/deadlines.py | 53 +
.../plugins/resources/installation.py | 41 +-
.../plugins/resources/lifecycle.py | 64 +-
.../capabilities/plugins/resources/owners.py | 66 +
.../capabilities/plugins/resources/prewarm.py | 177 +
.../plugins/resources/reservation.py | 84 +
.../capabilities/plugins/resources/service.py | 460 ++
.../plugins/resources/store.py | 2 +-
.../plugins/resources/stream.py | 58 +-
.../plugins/resources/transport.py | 0
.../plugins/runtime/__init__.py | 28 +-
.../plugins/runtime/activation_history.py | 2 +-
.../plugins/runtime/models.py | 0
.../plugins/runtime/resolution.py | 27 +-
.../plugins/runtime/sticky_selection.py | 35 +-
.../capabilities/{ref.py => reference.py} | 2 +-
.../core/capabilities/runtime/__init__.py | 37 -
src/backend/core/capabilities/skills.py | 656 ---
.../skills}/__init__.py | 1 -
.../capabilities/skills/backends/__init__.py | 14 +
.../skills}/backends/composite.py | 6 +-
.../skills}/backends/database.py | 2 +-
.../skills}/backends/filesystem.py | 2 +-
.../skills}/backends/protocol.py | 0
.../skills}/binary_files.py | 0
.../core/capabilities/skills/cache_refresh.py | 50 +
.../skills}/config.py | 56 +-
.../core/capabilities/skills/deps/__init__.py | 1 +
.../skills/deps/aggregator.py} | 20 +-
.../skills/deps/detector.py} | 0
.../skills/deps/prober.py} | 4 +-
.../skills/deps/requests.py} | 108 +-
.../skills/icons.py} | 0
.../core/capabilities/skills/installation.py | 117 +
.../skills}/loader.py | 165 +-
.../capabilities/skills/loader_factory.py | 21 +
.../skills/management.py} | 18 +-
.../skills}/publication.py | 41 +-
.../skills}/registry.py | 12 +-
.../capabilities/skills/runtime_loader.py | 115 +
.../capabilities/skills/script_discovery.py | 65 +
.../skills}/selection_record.py | 4 +-
.../skills}/skill_archive.py | 8 +-
.../core/capabilities/skills/zip_reader.py | 124 +
.../core/channels/adapters/dingtalk.py | 854 ---
src/backend/core/channels/adapters/lark.py | 683 ---
src/backend/core/channels/inbound.py | 1005 ----
.../{services => chat}/audio_transcription.py | 2 +-
src/backend/core/chat/compaction/__init__.py | 1 +
.../compaction/budget_checkpoint.py} | 4 +-
.../compaction/coordinator.py} | 39 +-
.../compaction/history.py} | 8 +-
src/backend/core/chat/compaction/manual.py | 68 +
.../compaction/progress.py} | 0
.../compaction/service.py} | 20 +-
.../compaction/summary.py} | 19 +-
.../compaction/usage_projection.py} | 4 +-
.../core/chat/compaction_checkpoints.py | 259 +
src/backend/core/chat/compaction_snapshots.py | 273 +
src/backend/core/chat/compaction_types.py | 36 +
src/backend/core/chat/context.py | 94 +-
.../chat_edition.py => chat/edition.py} | 0
.../{orchestration => core/chat}/followups.py | 57 +-
.../chat_fork_service.py => chat/fork.py} | 2 +-
src/backend/core/chat/history_operations.py | 49 +
src/backend/core/chat/limits.py | 88 +
.../chat}/message_parser.py | 5 +-
src/backend/core/chat/message_queries.py | 91 +
src/backend/core/chat/message_writes.py | 299 ++
src/backend/core/chat/mode_seed.py | 51 +
src/backend/core/chat/mode_types.py | 48 +
src/backend/core/chat/mode_validation.py | 107 +
.../chat_mode_service.py => chat/modes.py} | 295 +-
.../operations}/__init__.py | 0
src/backend/core/chat/operations/admission.py | 317 ++
.../operations}/admission_errors.py | 2 +-
.../operations}/agent_targets.py | 16 +-
.../operations}/commands.py | 18 +-
.../operations}/history.py | 10 +-
.../operations}/invocation.py | 31 +-
.../operations}/projects.py | 6 +-
.../operations}/request_context.py | 25 +-
.../operations}/runs.py | 14 +-
.../operations}/schemas.py | 9 +-
.../operations}/session_context.py | 58 +-
.../operations}/sessions.py | 40 +-
.../plan_service.py => chat/plans.py} | 0
.../core/{services => chat}/project_init.py | 8 +-
src/backend/core/chat/projection/__init__.py | 0
src/backend/core/chat/projection/codec.py | 8 +
.../chat/projection/reducer.py} | 134 +-
.../chat/projection/storage.py} | 8 +-
.../chat/projection/subscription.py} | 32 +-
.../public_chat_service.py => chat/public.py} | 43 +-
.../references.py} | 6 +-
src/backend/core/chat/runtime/__init__.py | 0
src/backend/core/chat/runtime/admission.py | 98 +
src/backend/core/chat/runtime/chunks.py | 183 +
.../chat/runtime/compaction.py} | 23 +-
src/backend/core/chat/runtime/completion.py | 231 +
src/backend/core/chat/runtime/followups.py | 64 +
src/backend/core/chat/runtime/handoff.py | 272 +
.../chat/runtime}/job_wakeup.py | 224 +-
src/backend/core/chat/runtime/pauses.py | 58 +
src/backend/core/chat/runtime/recovery.py | 179 +
.../core/chat/runtime/recovery_snapshot.py | 204 +
src/backend/core/chat/runtime/snapshots.py | 201 +
src/backend/core/chat/runtime/steering.py | 199 +
src/backend/core/chat/runtime/turn_state.py | 44 +
src/backend/core/chat/runtime/worker.py | 405 ++
src/backend/core/chat/schemas.py | 40 +-
.../chat_sequencer.py => chat/sequencer.py} | 41 +-
src/backend/core/chat/service.py | 256 +
src/backend/core/chat/session_operations.py | 284 +
.../core/{services => chat}/sidebar_status.py | 0
.../chat_steer_service.py => chat/steer.py} | 7 +-
src/backend/core/chat/steer_hook.py | 70 +
.../core/{services => chat}/steer_queue.py | 0
.../subagent_routing.py} | 0
.../chat_titles.py => chat/titles.py} | 21 +-
src/backend/core/chat/tool_log.py | 2 +-
.../chat}/tool_payloads.py | 93 +-
src/backend/core/config/__init__.py | 11 +-
src/backend/core/config/display_names.py | 2 +-
.../{edition_display_names.py => edition.py} | 8 +
src/backend/core/config/environment.py | 3 -
src/backend/core/config/identity_settings.py | 11 +-
src/backend/core/config/mcp_config.py | 8 +-
src/backend/core/config/product_settings.py | 5 +-
src/backend/core/config/runtime_env.py | 49 +-
src/backend/core/config/runtime_settings.py | 33 +-
src/backend/core/config/sandbox_settings.py | 11 +-
src/backend/core/config/settings.py | 7 +-
src/backend/core/config/store/__init__.py | 1 +
src/backend/core/config/store/config_keys.py | 12 +
.../core/config/store/execution_options.py | 93 +
src/backend/core/config/store/runtime_env.py | 52 +
.../core/config/store/system_config.py | 120 +
.../config/store/system_config_constants.py | 311 ++
src/backend/core/config/user_intros.py | 2 +-
src/backend/core/content/content_blocks.py | 701 ---
src/backend/core/content/file_parser.py | 752 ---
src/backend/core/db/__init__.py | 28 +-
src/backend/core/db/artifact_scope.py | 13 +
src/backend/core/db/chat_search.py | 2 +-
src/backend/core/db/edition.py | 27 +
src/backend/core/db/engine.py | 45 +-
src/backend/core/db/history_projection.py | 2 +-
src/backend/core/db/models/__init__.py | 212 +-
src/backend/core/db/models/admin.py | 338 +-
src/backend/core/db/models/agent.py | 73 +-
src/backend/core/db/models/artifact.py | 17 +-
src/backend/core/db/models/channel_relay.py | 40 -
src/backend/core/db/models/chat.py | 350 +-
src/backend/core/db/models/chat_mode.py | 5 +-
src/backend/core/db/models/chat_resources.py | 116 +
src/backend/core/db/models/chat_runs.py | 199 +
src/backend/core/db/models/column_types.py | 9 +
src/backend/core/db/models/config.py | 12 +-
src/backend/core/db/models/evolution.py | 10 +-
src/backend/core/db/models/identity.py | 285 +-
src/backend/core/db/models/job.py | 14 +-
src/backend/core/db/models/kb_wiki.py | 224 -
src/backend/core/db/models/knowledge.py | 182 +-
src/backend/core/db/models/logs.py | 13 +-
src/backend/core/db/models/memory.py | 15 +-
src/backend/core/db/models/observability.py | 61 -
src/backend/core/db/models/ontology.py | 6 +-
src/backend/core/db/models/project.py | 14 +-
src/backend/core/db/personal_file_names.py | 13 +-
...extensions.py => personal_model_fields.py} | 0
.../{edition_tables.py => personal_schema.py} | 6 +-
src/backend/core/db/repository/__init__.py | 39 +-
src/backend/core/db/repository/artifact.py | 8 +-
src/backend/core/db/repository/catalog.py | 5 +-
src/backend/core/db/repository/chat.py | 15 +-
src/backend/core/db/repository/folder.py | 40 +
.../{agent.py => personal_agent.py} | 0
.../{audit.py => personal_audit.py} | 2 +-
src/backend/core/db/repository/user.py | 115 +-
.../core/db/site_storage_retirement.py | 8 +-
src/backend/core/db/system_configuration.py | 146 +
src/backend/core/db/threads.py | 99 +
src/backend/core/db/user_cleanup.py | 13 +
src/backend/core/evolution/__init__.py | 6 +-
src/backend/core/evolution/activation.py | 1502 ------
src/backend/core/evolution/agent_profile.py | 505 --
.../core/evolution/assessment/__init__.py | 1 +
.../{ => assessment}/applicability.py | 2 +-
.../evolution/{ => assessment}/benchmark.py | 6 +-
.../evolution/{ => assessment}/evaluator.py | 0
.../evolution/{ => assessment}/experiments.py | 2 +-
.../{ => assessment}/fault_injection.py | 15 +-
.../evolution/{ => assessment}/metrics.py | 0
.../evolution/{ => assessment}/similarity.py | 9 +-
.../evolution/{ => assessment}/sop_judge.py | 2 +-
src/backend/core/evolution/backfill.py | 9 +-
src/backend/core/evolution/contract.py | 4 +-
src/backend/core/evolution/cycle/__init__.py | 1 +
.../core/evolution/cycle/activation.py | 353 ++
.../evolution/cycle/activation_constants.py | 11 +
.../core/evolution/cycle/activation_policy.py | 196 +
.../core/evolution/cycle/activation_types.py | 9 +
.../core/evolution/cycle/candidate_records.py | 197 +
.../evolution/cycle/composition_candidates.py | 221 +
.../core/evolution/cycle/cycle_extras.py | 32 +
.../evolution/cycle/cycle_extras_constants.py | 20 +
.../core/evolution/cycle/episode_views.py | 178 +
.../core/evolution/cycle/evaluation.py | 153 +
.../core/evolution/cycle/extra_findings.py | 166 +
.../core/evolution/cycle/graph_candidates.py | 156 +
src/backend/core/evolution/cycle/loop.py | 292 ++
.../core/evolution/cycle/memory_candidates.py | 286 +
.../core/evolution/cycle/memory_findings.py | 109 +
src/backend/core/evolution/cycle/ordering.py | 103 +
src/backend/core/evolution/cycle/patterns.py | 113 +
.../core/evolution/cycle/profile_release.py | 281 +
src/backend/core/evolution/cycle/promotion.py | 317 ++
.../evolution/cycle/promotion_constants.py | 80 +
.../core/evolution/cycle/promotion_types.py | 94 +
.../core/evolution/cycle/prompt_candidates.py | 95 +
.../core/evolution/{ => cycle}/readiness.py | 25 +-
.../core/evolution/{ => cycle}/release.py | 0
.../core/evolution/cycle/retirement.py | 158 +
.../evolution/{ => cycle}/runtime_binding.py | 85 +-
.../core/evolution/cycle/skill_document.py | 298 ++
.../core/evolution/cycle/skill_release.py | 355 ++
.../core/evolution/{ => cycle}/timescales.py | 2 +-
.../core/evolution/{ => cycle}/trigger.py | 77 +-
.../core/evolution/cycle/usage_evidence.py | 283 +
src/backend/core/evolution/cycle_extras.py | 653 ---
.../core/evolution/evidence/__init__.py | 1 +
.../core/evolution/{ => evidence}/cassette.py | 19 +-
.../{ => evidence}/counterfactual.py | 2 +-
.../core/evolution/{ => evidence}/credit.py | 2 +-
.../core/evolution/{ => evidence}/events.py | 2 +-
.../{ => evidence}/evidence_contract.py | 0
.../{ => evidence}/evidence_resolver.py | 8 +-
.../core/evolution/{ => evidence}/exposure.py | 10 +-
.../core/evolution/{ => evidence}/lineage.py | 2 +-
.../core/evolution/{ => evidence}/outcomes.py | 0
.../core/evolution/{ => evidence}/replay.py | 0
.../{ => evidence}/trace_assembler.py | 6 +-
.../evolution/{ => evidence}/trace_store.py | 4 +-
.../core/evolution/generators/__init__.py | 1 +
.../evolution/{ => generators}/graph_scan.py | 0
.../{ => generators}/memory_apply.py | 8 +-
.../evolution/{ => generators}/memory_ops.py | 2 +-
.../evolution/{ => generators}/memory_scan.py | 2 +-
.../{ => generators}/ontology_bridge.py | 0
.../{ => generators}/orchestration_gen.py | 2 +-
.../evolution/{ => generators}/prompt_gen.py | 0
.../skill_candidate_engine.py | 14 +-
.../evolution/generators/skill_drafting.py | 185 +
.../evolution/generators/skill_evidence.py | 124 +
.../core/evolution/generators/skill_gen.py | 95 +
.../generators/skill_gen_constants.py | 109 +
src/backend/core/evolution/loop.py | 1576 ------
.../core/evolution/profiles/__init__.py | 1 +
.../core/evolution/profiles/agent_profile.py | 259 +
.../profiles/agent_profile_constants.py | 102 +
.../core/evolution/profiles/change_preview.py | 85 +
.../evolution/{ => profiles}/console_view.py | 2 -
.../evolution/{ => profiles}/federation.py | 2 +-
.../core/evolution/{ => profiles}/personal.py | 52 +-
.../personal_review.py} | 219 +-
.../core/evolution/{ => profiles}/policies.py | 22 +-
.../core/evolution/{ => profiles}/prefs.py | 0
.../evolution/profiles/profile_selection.py | 147 +
.../core/evolution/profiles/stall_hook.py | 103 +
.../core/evolution/profiles/user_settings.py | 70 +
.../profiles/user_settings_constants.py | 14 +
src/backend/core/evolution/promotion.py | 890 ----
.../core/evolution/settlement/__init__.py | 1 +
.../evolution/settlement/memory_service.py | 39 +
.../evolution/{ => settlement}/settlement.py | 0
.../{ => settlement}/settlement_runner.py | 43 +-
.../{ => settlement}/settlement_store.py | 2 +-
src/backend/core/evolution/skill_gen.py | 521 --
src/backend/core/extensions/__init__.py | 69 -
src/backend/core/extensions/registry.py | 107 -
src/backend/core/files/__init__.py | 1 +
src/backend/core/files/artifact_bytes.py | 75 +
.../core/{ => files}/artifacts/__init__.py | 1 -
.../artifacts/backfill.py} | 10 +-
src/backend/core/files/artifacts/generated.py | 210 +
.../artifacts/key_repair.py} | 0
.../{ => files}/artifacts/local_reference.py | 31 +-
.../artifacts/location.py} | 24 +-
.../artifacts/reader.py} | 19 +-
.../artifacts/refs.py} | 10 +-
.../artifacts/service.py} | 15 +-
.../core/{ => files}/artifacts/store.py | 26 +-
.../artifacts/summary.py} | 8 +-
.../artifacts/text.py} | 15 +-
src/backend/core/files/content/__init__.py | 1 +
.../{ => files}/content/archive_browser.py | 2 +-
.../{ => files}/content/archive_limits.py | 0
src/backend/core/files/content/branding.py | 299 ++
.../core/files/content/content_blocks.py | 183 +
.../files/content/content_blocks_constants.py | 181 +
.../core/files/content/file_formats.py | 33 +
src/backend/core/files/content/file_parser.py | 41 +
.../{ => files}/content/file_validation.py | 0
.../core/{ => files}/content/office.py | 0
.../content/office_preview_cache.py | 2 +-
.../core/files/content/parser_config.py | 47 +
src/backend/core/files/content/pdf_parser.py | 125 +
.../core/files/content/presentation_parser.py | 63 +
.../core/files/content/spreadsheet_parser.py | 332 ++
.../core/{ => files}/content/svg_fit.py | 5 +-
src/backend/core/files/content/text_parser.py | 21 +
src/backend/core/files/content/word_parser.py | 77 +
src/backend/core/files/edition.py | 98 +
.../core/{services => files}/folder_batch.py | 47 +-
.../personal_operations.py} | 10 +-
.../core/{ => files}/spaces/__init__.py | 0
src/backend/core/{ => files}/spaces/access.py | 4 +-
src/backend/core/{ => files}/spaces/files.py | 8 +-
src/backend/core/{ => files}/spaces/leases.py | 24 +-
src/backend/core/{ => files}/spaces/mounts.py | 32 +-
.../core/{ => files}/spaces/projects.py | 6 +-
.../core/{ => files}/spaces/recovery.py | 4 +-
.../core/{ => files}/spaces/sandbox.py | 26 +-
src/backend/core/{ => files}/spaces/search.py | 2 +-
src/backend/core/{ => files}/spaces/text.py | 4 +-
src/backend/core/{ => files}/spaces/tree.py | 0
.../core/{ => files}/storage/__init__.py | 17 +-
.../core/{ => files}/storage/entries.py | 0
.../core/{ => files}/storage/factory.py | 32 +-
src/backend/core/{ => files}/storage/local.py | 8 +-
.../{ => files}/storage/local_directory.py | 4 +-
.../core/{ => files}/storage/local_paths.py | 2 +-
.../core/{ => files}/storage/protocol.py | 0
src/backend/core/{ => files}/storage/s3.py | 4 +-
.../core/{ => files}/storage/s3_directory.py | 2 +-
.../core/{services => files}/upload_retry.py | 4 +-
.../capability_errors.py} | 0
src/backend/core/harness/context_items.py | 6 +
src/backend/core/harness/events.py | 67 +-
.../{llm => harness}/execution_manifest.py | 2 +-
src/backend/core/harness/hooks.py | 151 +-
.../harness_ledger.py => harness/ledger.py} | 163 +-
src/backend/core/harness/log_audit.py | 144 +
src/backend/core/harness/log_context.py | 161 +
src/backend/core/harness/log_records.py | 212 +
src/backend/core/harness/log_writer.py | 121 +
.../core/harness/log_writer_constants.py | 32 +
.../core/harness/observation_writer.py | 134 +
src/backend/core/harness/registry/__init__.py | 79 +
.../core/harness/registry/authentication.py | 39 +
.../core/harness/registry/build_governance.py | 51 +
.../harness/registry/cache_invalidation.py | 27 +
.../harness/registry/capability_management.py | 27 +
.../harness/registry/capability_revisions.py | 34 +
.../harness/registry/capability_runtime.py | 74 +
.../core/harness/registry/catalog_options.py | 26 +
.../core/harness/registry/chat_execution.py | 27 +
.../core/harness/registry/chat_history.py | 21 +
.../core/harness/registry/compaction.py | 62 +
.../core/harness/registry/device_catalog.py | 79 +
.../registry}/edition.py | 0
.../core/harness/registry/evaluation.py | 72 +
.../harness/registry/execution_account.py | 35 +
.../harness/registry/execution_catalog.py | 51 +
.../harness/registry/execution_identity.py | 78 +
.../core/harness/registry/hook_factories.py | 69 +
.../core/harness/registry/host_runtime.py | 13 +
.../core/harness/registry/job_operations.py | 21 +
.../core/harness/registry/local_files.py | 65 +
.../harness/registry/local_publication.py | 69 +
.../harness/registry/marketplace_catalog.py | 44 +
.../core/harness/registry/mcp_clients.py | 21 +
.../harness/registry/model_credentials.py | 70 +
.../core/harness/registry/model_services.py | 59 +
.../core/harness/registry/package_sources.py | 48 +
.../core/harness/registry/package_views.py | 66 +
src/backend/core/harness/registry/registry.py | 169 +
.../core/harness/registry/resource_owners.py | 48 +
.../harness/registry/retrieval_context.py | 37 +
.../core/harness/registry/run_cancellation.py | 37 +
.../core/harness/registry/run_capabilities.py | 114 +
.../core/harness/registry/schema_lifecycle.py | 36 +
.../core/harness/registry/skill_content.py | 24 +
.../harness/registry/system_configuration.py | 31 +
.../core/harness/registry/tool_operations.py | 59 +
.../registry/transfer_channels.py} | 64 +-
.../core/harness/registry/user_cleanup.py | 27 +
src/backend/core/harness/tool_call_context.py | 5 +
.../core/harness/tool_effect_context.py | 18 +
src/backend/core/harness/usage.py | 41 +-
src/backend/core/infra/background.py | 18 +-
src/backend/core/infra/byte_range.py | 71 +
src/backend/core/infra/crypto.py | 28 +-
.../junction.py => infra/directory_links.py} | 32 +-
src/backend/core/infra/dns_cache.py | 58 +-
src/backend/core/infra/failures.py | 13 +
.../lockfile.py => infra/file_lock.py} | 0
src/backend/core/{ => infra}/immutable.py | 0
src/backend/core/infra/json_output.py | 49 +
src/backend/core/infra/leader.py | 225 +-
.../core/infra/local_runtime/__init__.py | 1 +
.../infra/local_runtime/dynamic_servers.py | 64 +
.../infra/local_runtime/lifecycle.py} | 33 +-
.../local_runtime/runner_auth.py} | 0
.../infra/local_runtime/startup.py} | 95 +-
src/backend/core/infra/model_output.py | 95 +
src/backend/core/infra/probes/__init__.py | 1 +
.../probes/edition.py} | 0
.../probes}/service_probes.py | 65 +-
src/backend/core/infra/rate_limit.py | 21 -
src/backend/core/infra/redis.py | 27 +-
src/backend/core/infra/runtime_state.py | 24 +-
.../text_bounds.py} | 0
src/backend/core/infra/time.py | 14 +-
src/backend/core/infra/worker_count.py | 9 +-
.../{orchestration => core}/jobs/__init__.py | 0
.../{orchestration => core}/jobs/agent.py | 14 +-
.../{orchestration => core}/jobs/callback.py | 0
.../{orchestration => core}/jobs/files.py | 6 +-
.../jobs/notifications.py | 22 +-
.../{orchestration => core}/jobs/owner.py | 43 +-
.../{orchestration => core}/jobs/process.py | 13 +-
src/backend/core/jobs/reminder_hook.py | 80 +
.../{orchestration => core}/jobs/runner.py | 0
.../job_runtime.py => core/jobs/runtime.py} | 41 +-
.../{orchestration => core}/jobs/sdk.py | 0
.../{orchestration => core}/jobs/state.py | 2 +-
.../job_service.py => jobs/store.py} | 0
.../jobs/supervisor.py | 38 +-
src/backend/core/jobs/tool_operations.py | 410 ++
src/backend/core/kb/kb_assets.py | 567 --
src/backend/core/kb/kb_parser.py | 933 ----
src/backend/core/kb/kb_vector.py | 755 ---
src/backend/core/kb/wiki/local_provider.py | 523 --
src/backend/core/llm/__init__.py | 14 +-
src/backend/core/llm/_distill_shared.py | 156 -
src/backend/core/llm/api_runtime/__init__.py | 1 +
.../agent.py} | 6 +-
.../agent_skills.py} | 2 +-
.../agent_tools.py} | 27 +-
.../project.py} | 2 +-
src/backend/core/llm/capability_tools.py | 106 -
src/backend/core/llm/citations/__init__.py | 1 +
src/backend/core/llm/citations/allocator.py | 115 +
src/backend/core/llm/citations/anchor.py | 63 +
.../core/llm/citations/anchor_state.py | 101 +
src/backend/core/llm/citations/extraction.py | 208 +
src/backend/core/llm/citations/hook.py | 38 +
src/backend/core/llm/citations/types.py | 63 +
src/backend/core/llm/command_middleware.py | 39 -
src/backend/core/llm/context/__init__.py | 1 +
src/backend/core/llm/context/adapter.py | 362 ++
.../core/llm/context/attachment_hook.py | 81 +
.../context/budget.py} | 2 +-
.../llm/{ => context}/compacting_agent.py | 20 +-
.../core/llm/{ => context}/compaction.py | 11 +-
src/backend/core/llm/context/content.py | 212 +
src/backend/core/llm/context/ir.py | 327 ++
src/backend/core/llm/context/ir_constants.py | 86 +
src/backend/core/llm/context/items.py | 215 +
.../core/llm/context/message_defaults.py | 78 +
.../core/llm/{ => context}/offloader.py | 4 +-
.../core/llm/context/provider_conversion.py | 239 +
.../core/llm/context/reasoning_hooks.py | 49 +
.../core/llm/context/reference_conversion.py | 79 +
src/backend/core/llm/context/rendering.py | 114 +
.../{context_safety.py => context/safety.py} | 16 +-
.../core/llm/{ => context}/system_reminder.py | 4 +-
.../{context_usage.py => context/usage.py} | 2 +-
.../core/llm/context/workspace_hooks.py | 125 +
src/backend/core/llm/context_adapter.py | 867 ---
src/backend/core/llm/context_ir.py | 855 ---
src/backend/core/llm/factory/README.md | 6 +-
src/backend/core/llm/factory/__init__.py | 6 +-
src/backend/core/llm/factory/assembly.py | 18 +-
src/backend/core/llm/factory/build.py | 6 +-
src/backend/core/llm/factory/defaults.py | 16 +-
.../core/llm/factory/prompts/policy.py | 3 +-
.../core/llm/factory/prompts/system.py | 39 +-
src/backend/core/llm/factory/request.py | 4 +-
.../llm/factory/runtime/agent_construction.py | 21 +-
.../capability_bindings.py} | 11 +-
.../llm/factory/runtime/execution_budget.py | 10 +-
.../core/llm/factory/runtime/listeners.py | 33 +-
.../llm/factory/runtime/manifest_binding.py | 4 +-
.../llm/factory/runtime/middleware_stack.py | 130 +-
.../llm/factory/runtime/model_selection.py | 20 +-
.../llm/factory/selection/capabilities.py | 58 +-
.../llm/factory/selection/entry_policy.py | 20 +-
.../llm/factory/selection/mode_bindings.py | 7 +-
.../factory/selection/profile_selection.py | 12 +-
.../factory/selection/progressive_bindings.py | 24 +-
.../llm/factory/selection/server_selection.py | 10 +-
.../factory/selection/sticky_capabilities.py | 26 +-
.../factory/selection/subagent_bindings.py | 8 +-
.../registry.py => core/llm/factory/specs.py} | 2 -
.../core/llm/factory/tools/definitions.py | 35 +
.../core/llm/factory/tools/interactive.py | 2 +-
.../core/llm/factory/tools/mcp_config.py | 55 +-
.../core/llm/factory/tools/mcp_connections.py | 226 +-
src/backend/core/llm/factory/tools/native.py | 82 +-
.../llm/factory/tools/skill_registration.py | 26 +-
.../core/llm/factory/tools/tool_surface.py | 46 +-
src/backend/core/llm/finish_guard.py | 85 -
src/backend/core/llm/hooks.py | 588 ---
src/backend/core/llm/host_services.py | 31 +
src/backend/core/llm/management_tool.py | 84 -
src/backend/core/llm/mcp/__init__.py | 1 +
src/backend/core/llm/mcp/callbacks.py | 168 +
src/backend/core/llm/mcp/chat_sessions.py | 181 +
src/backend/core/llm/mcp/client.py | 149 +
src/backend/core/llm/mcp/context_tools.py | 69 +
src/backend/core/llm/mcp/deadline.py | 40 +
.../{mcp_invocation.py => mcp/invocation.py} | 10 +-
src/backend/core/llm/mcp/lifetime.py | 68 +
src/backend/core/llm/mcp/manager.py | 62 +
src/backend/core/llm/mcp/results.py | 67 +
src/backend/core/llm/mcp/session.py | 147 +
src/backend/core/llm/mcp/tools.py | 113 +
src/backend/core/llm/mcp_manager.py | 336 --
src/backend/core/llm/mcp_pool.py | 387 --
src/backend/core/llm/middleware/__init__.py | 1 +
.../migration_map.py} | 67 +-
src/backend/core/llm/middlewares.py | 1450 -----
src/backend/core/llm/persona_distiller.py | 226 -
src/backend/core/llm/ports.py | 79 +
.../llm/project_environment_middleware.py | 27 -
src/backend/core/llm/prompt_files.py | 138 +
src/backend/core/llm/prompt_kinds.py | 193 +
src/backend/core/llm/prompt_rendering.py | 137 +
src/backend/core/llm/prompt_storage.py | 99 +
src/backend/core/llm/prompt_versions.py | 296 ++
src/backend/core/llm/prompt_versions_state.py | 30 +
src/backend/core/llm/providers/__init__.py | 13 -
src/backend/core/llm/runtime/__init__.py | 1 +
.../{ => runtime}/agentscope_hook_adapter.py | 223 +-
.../core/llm/runtime/context_hook_adapter.py | 80 +
.../core/llm/runtime/delta_coalescer.py | 106 +
src/backend/core/llm/runtime/file_context.py | 277 +
src/backend/core/llm/runtime/finish_guard.py | 54 +
.../core/llm/runtime/hooks_constants.py | 17 +
.../llm/{ => runtime}/human_interaction.py | 4 +-
.../llm/{ => runtime}/interaction_store.py | 0
.../core/llm/{ => runtime}/manifest_agent.py | 17 +-
.../core/llm/{ => runtime}/message_compat.py | 16 +-
src/backend/core/llm/runtime/model_cache.py | 133 +
.../llm/runtime/model_selection_adapter.py | 28 +
.../core/llm/{ => runtime}/model_steps.py | 5 +-
.../llm/{ => runtime}/project_environment.py | 33 +-
.../llm/runtime/project_environment_hook.py | 19 +
.../core/llm/runtime/review_text_agent.py | 51 +
.../llm/{ => runtime}/session_capabilities.py | 2 +-
src/backend/core/llm/runtime/state.py | 75 +
src/backend/core/llm/runtime/stream_events.py | 264 +
src/backend/core/llm/runtime/stream_loop.py | 332 ++
src/backend/core/llm/runtime/stream_text.py | 63 +
src/backend/core/llm/runtime/streaming.py | 287 +
.../core/llm/runtime/streaming_constants.py | 20 +
.../llm/runtime}/tool_callbacks.py | 24 +-
.../llm/runtime/tool_execution_adapter.py | 210 +
.../core/llm/runtime/tool_hook_adapter.py | 81 +
.../llm/{ => runtime}/tool_media_store.py | 24 +-
.../core/llm/runtime/tool_result_adapter.py | 53 +
src/backend/core/llm/runtime/turn_state.py | 41 +
.../core/llm/{ => runtime}/workspace.py | 0
.../selector.py => llm/skill_selection.py} | 23 +-
src/backend/core/llm/steer_middleware.py | 96 -
src/backend/core/llm/subagents/__init__.py | 1 +
.../execution.py} | 48 +-
.../prompts.py} | 0
.../sessions.py} | 2 +-
.../stream.py} | 39 +-
.../{subagent_tool.py => subagents/tool.py} | 72 +-
src/backend/core/llm/tool_collector.py | 270 -
.../core/llm/tool_permissions/__init__.py | 95 +-
src/backend/core/llm/tool_permissions/hook.py | 71 +
.../core/llm/tool_permissions/middleware.py | 86 -
.../core/llm/tool_permissions/pending.py | 4 +-
.../core/llm/tool_permissions/service.py | 47 +-
src/backend/core/llm/tools/__init__.py | 9 +-
src/backend/core/llm/tools/_common.py | 23 +-
src/backend/core/llm/tools/_tool_helpers.py | 208 +-
.../core/llm/tools/ask_user_question_tool.py | 11 +-
.../core/llm/tools/chat_history_tool.py | 51 +-
.../cloud_gateway.py} | 119 +-
src/backend/core/llm/tools/cloud_manifest.py | 55 +
.../__init__.py | 0
.../gate.py | 3 +-
.../pending.py | 13 +-
.../shared.py | 2 +-
.../state.py | 104 +-
.../core/llm/tools/data_context_tool.py | 96 -
src/backend/core/llm/tools/edit_tool.py | 25 +-
src/backend/core/llm/tools/glob_tool.py | 7 +-
src/backend/core/llm/tools/grep_tool.py | 12 +-
src/backend/core/llm/tools/job_tool.py | 383 +-
src/backend/core/llm/tools/management.py | 45 +
.../tools/ontology_toolkit.py} | 30 +-
src/backend/core/llm/tools/pin_tool.py | 46 +-
.../plan_update.py} | 19 +-
.../tools/plugin_activation.py} | 122 +-
.../llm/tools/project_instructions_tool.py | 30 +-
.../core/llm/tools/project_source_access.py | 6 +-
.../core/llm/tools/read_artifact_tool.py | 42 +-
src/backend/core/llm/tools/read_image_tool.py | 34 +-
src/backend/core/llm/tools/read_tool.py | 27 +-
.../core/llm/tools/sandbox_artifact_tools.py | 34 +-
src/backend/core/llm/tools/sandbox_tool.py | 75 +-
src/backend/core/llm/tools/skill_tool.py | 51 +-
src/backend/core/llm/tools/user_questions.py | 67 +-
src/backend/core/llm/tools/write_tool.py | 30 +-
src/backend/core/memory/access.py | 48 +-
src/backend/core/memory/audit.py | 166 +-
src/backend/core/memory/backend.py | 96 +-
src/backend/core/memory/context.py | 2 +-
src/backend/core/memory/edition.py | 4 +
src/backend/core/memory/effect_lane.py | 75 +-
src/backend/core/memory/executor.py | 5 +-
src/backend/core/memory/extractors/_base.py | 49 +-
src/backend/core/memory/extractors/graph.py | 2 +-
src/backend/core/memory/extractors/writers.py | 10 +-
src/backend/core/memory/graph.py | 345 +-
src/backend/core/memory/graph_contract.py | 122 +
src/backend/core/memory/graph_state.py | 141 +
src/backend/core/memory/outbox/enqueue.py | 3 +-
src/backend/core/memory/outbox/jobs.py | 35 +-
src/backend/core/memory/outbox/leases.py | 6 +-
src/backend/core/memory/outbox/settlement.py | 18 +-
src/backend/core/memory/outbox/worker.py | 71 +-
src/backend/core/memory/pipeline.py | 31 +-
src/backend/core/memory/preview.py | 5 +-
src/backend/core/memory/procedures.py | 3 +-
src/backend/core/memory/queries.py | 3 +-
src/backend/core/memory/sanitizer.py | 39 +-
src/backend/core/memory/service.py | 9 +-
src/backend/core/memory/session_snapshot.py | 2 +-
.../settings.py} | 2 +-
src/backend/core/memory/settlement_port.py | 33 +
src/backend/core/memory/ttl_sweeper.py | 2 +-
src/backend/core/memory/weights.py | 23 +-
src/backend/core/models/__init__.py | 1 +
src/backend/core/models/admission.py | 21 +
.../core/models/api_protocol_backfill.py | 106 +
.../core/{llm => models}/chat_models.py | 47 +-
.../core/models/completions/__init__.py | 1 +
.../{llm => models/completions}/classifier.py | 25 +-
.../completions}/single_turn.py | 10 +-
.../{llm => models/completions}/summarizer.py | 25 +-
.../model_config.py => models/config.py} | 205 +-
src/backend/core/models/constants.py | 5 +
.../context_window.py} | 2 +-
src/backend/core/models/embedding.py | 123 +
src/backend/core/models/embedding_state.py | 20 +
src/backend/core/{llm => models}/failover.py | 36 +-
.../import_validation.py} | 4 +-
src/backend/core/models/providers/__init__.py | 3 +
.../providers/_image_tokens.py | 2 +-
.../providers/context_probe.py | 2 +-
.../providers/openai_compat.py | 20 +-
.../providers/protocol_probe.py | 0
.../providers/reasoning_probe.py | 6 +-
.../{llm => models}/providers/registry.py | 2 +-
.../{llm => models}/providers/stream_retry.py | 4 +-
.../providers/vendor_models.py | 12 +-
.../core/{llm => models}/reasoning_effort.py | 0
.../core/{llm => models}/reasoning_replay.py | 0
.../repository.py} | 4 +-
src/backend/core/models/reranking.py | 63 +
.../responses.py} | 97 +-
.../selection.py} | 46 +-
src/backend/core/models/selection_hook.py | 20 +
.../{llm => models}/tool_call_identity.py | 0
.../core/{llm => models}/tool_result_media.py | 63 +-
.../{llm/model_usage.py => models/usage.py} | 4 +-
src/backend/core/models/vision/__init__.py | 34 +
.../core/{ => models}/vision/attachments.py | 18 +-
.../core/{ => models}/vision/prompt.py | 2 +-
.../core/{ => models}/vision/provider.py | 12 +-
.../core/{ => models}/vision/render.py | 4 +-
.../core/{ => models}/vision/schema.py | 0
.../core/{ => models}/vision/service.py | 14 +-
src/backend/core/myspace/duplicate_cleanup.py | 63 -
src/backend/core/ontology/__init__.py | 3 +-
src/backend/core/ontology/audit_events.py | 71 +
src/backend/core/ontology/build_validator.py | 6 +-
.../evolution.py} | 115 +-
src/backend/core/ontology/host.py | 13 +
src/backend/core/ontology/manual_review.py | 85 +
.../core/ontology/output_review_state.py | 60 +
.../ontology_policy.py => ontology/policy.py} | 4 +-
.../core/ontology/review_operations.py | 102 +
src/backend/core/ontology/review_prompt.py | 99 +
src/backend/core/ontology/reviewer.py | 319 ++
src/backend/core/ontology/reviewer_state.py | 19 +
src/backend/core/ontology/runtime_gates.py | 157 +
src/backend/core/ontology/runtime_pack.py | 357 ++
src/backend/core/ontology/runtime_policy.py | 158 +
.../service.py} | 262 +-
src/backend/core/ontology/tool_hook.py | 183 +
.../core/ontology/validation_models.py | 33 +
src/backend/core/ontology/validator.py | 634 +--
src/backend/core/plugins/README.md | 28 -
.../core/plugins/management/__init__.py | 80 -
.../core/plugins/resources/application.py | 152 -
.../plugins/resources/application_cleanup.py | 48 -
.../core/plugins/resources/deadlines.py | 125 -
src/backend/core/plugins/resources/owners.py | 112 -
src/backend/core/plugins/resources/prewarm.py | 142 -
.../core/plugins/resources/reservation.py | 93 -
src/backend/core/plugins/resources/service.py | 347 --
src/backend/core/projects/__init__.py | 1 +
.../project_access.py => projects/access.py} | 2 +-
.../api.py} | 2 +-
.../project_service.py => projects/base.py} | 87 +-
src/backend/core/projects/edition.py | 8 +
.../environment}/__init__.py | 0
.../environment}/config.py | 0
.../environment}/dependencies.py | 2 +-
.../environment/service.py} | 12 +-
.../environment_resources.py} | 22 +-
.../files.py} | 14 +-
.../instructions.py} | 104 +-
src/backend/core/projects/local_resolve.py | 90 +
.../local_workspace.py} | 2 +-
.../personal_scope.py} | 8 +-
src/backend/core/projects/personal_summary.py | 44 +
.../personal_transfer.py} | 0
src/backend/core/projects/project_names.py | 27 +
.../project_source.py => projects/source.py} | 20 +-
.../user_folders.py} | 23 +-
src/backend/core/runs/__init__.py | 0
.../runs/api_recovery.py} | 6 +-
.../run_binding.py => runs/binding.py} | 105 +-
src/backend/core/runs/clock.py | 55 +
src/backend/core/runs/events/__init__.py | 0
src/backend/core/runs/events/projection.py | 13 +
.../runs/events/store.py} | 88 +-
src/backend/core/runs/events/subscription.py | 81 +
src/backend/core/runs/events/writer.py | 197 +
src/backend/core/runs/host.py | 41 +
.../run_journal.py => runs/journal.py} | 32 +-
.../journal_domain.py} | 0
.../journal_lifecycle.py} | 20 +-
.../journal_recovery.py} | 9 +-
src/backend/core/runs/ownership.py | 276 +
src/backend/core/runs/runtime.py | 296 ++
src/backend/core/runs/settings.py | 98 +
src/backend/core/runs/tasks.py | 23 +
.../runs}/tool_effect_recovery.py | 99 +-
.../core/runs/tool_effects/__init__.py | 1 +
.../core/runs/tool_effects/arguments.py | 65 +
src/backend/core/runs/tool_effects/errors.py | 43 +
src/backend/core/runs/tool_effects/gateway.py | 256 +
src/backend/core/runs/tool_effects/hooks.py | 14 +
src/backend/core/runs/tool_effects/host.py | 28 +
.../runs/tool_effects/intent_transactions.py | 342 ++
.../runs/tool_effects/invocation_claims.py | 123 +
src/backend/core/runs/tool_effects/journal.py | 223 +
.../runs/tool_effects/journal_constants.py | 32 +
.../core/runs/tool_effects/recovery.py | 176 +
.../core/runs/tool_effects/recovery_claims.py | 166 +
.../core/runs/tool_effects/registry.py | 95 +
.../runs/tool_effects/result_transactions.py | 285 +
.../core/runs/tool_effects/retention.py | 154 +
src/backend/core/runs/tool_effects/types.py | 74 +
src/backend/core/runs/watchdog.py | 322 ++
src/backend/core/sandbox/commands/__init__.py | 1 +
.../core/sandbox/commands/completion_hook.py | 44 +
.../{services => sandbox}/commands/context.py | 10 +-
.../commands/delivery.py | 13 +-
.../sandbox}/commands/events.py | 15 +-
.../sandbox}/commands/notifications.py | 27 +-
src/backend/core/sandbox/commands/runtime.py | 449 ++
.../{services => sandbox}/commands/store.py | 13 +-
.../sandbox}/commands/subscription.py | 44 +-
.../core/sandbox/credential_dispatch.py | 30 +
src/backend/core/sandbox/factory.py | 33 +-
src/backend/core/sandbox/oslayer/bwrap.py | 21 +-
src/backend/core/sandbox/oslayer/seatbelt.py | 34 +-
.../core/sandbox/oslayer/windows_runtime.py | 36 +-
.../permissions}/account.py | 4 +-
.../permissions/approval.py} | 6 +-
.../permissions/dispatch_models.py} | 4 +-
.../core/sandbox/permissions/os_gate.py | 35 +-
.../permissions/tool_specs.py} | 20 +-
.../core/sandbox/permissions/vocabulary.py | 26 +
src/backend/core/sandbox/processes/cloud.py | 28 +-
.../core/sandbox/processes/completion.py | 10 +-
.../core/sandbox/processes/dispatch.py | 2 +-
.../core/sandbox/processes/interactive.py | 3 +-
.../core/sandbox/providers/account_env.py | 27 +-
src/backend/core/sandbox/providers/edition.py | 2 +-
.../providers/opensandbox/account_volumes.py | 292 --
.../sandbox/providers/opensandbox/admin.py | 5 +-
.../sandbox/providers/opensandbox/adoption.py | 242 +-
.../sandbox/providers/opensandbox/binding.py | 2 +-
.../sandbox/providers/opensandbox/creation.py | 74 +-
.../providers/opensandbox/execution.py | 7 +-
.../providers/opensandbox/lifecycle.py | 4 +-
.../sandbox/providers/opensandbox/models.py | 9 +-
.../sandbox/providers/opensandbox/pool.py | 3 +-
.../sandbox/providers/opensandbox/provider.py | 59 +-
.../providers/opensandbox/reclamation.py | 4 +-
.../sandbox/providers/opensandbox/runtime.py | 25 +-
.../providers/opensandbox/snapshots.py | 44 +-
.../sandbox/providers/opensandbox/tracking.py | 31 +
.../providers/opensandbox/user_pool.py | 6 +-
.../sandbox/providers/opensandbox/volumes.py | 85 +-
.../providers/script_runner/provider.py | 44 +-
.../core/sandbox/runner/desktop_workspace.py | 16 +-
.../core/sandbox/runner/native_group.py | 28 +-
.../core/sandbox/runner/process_runtime.py | 4 +-
src/backend/core/sandbox/runner/server.py | 2 +-
src/backend/core/sandbox/runner/user_env.py | 198 +
src/backend/core/sandbox/sessions/registry.py | 21 +-
src/backend/core/sandbox/sessions/router.py | 15 +-
.../core/sandbox/workspace/container.py | 15 +-
.../core/sandbox/workspace/credentials.py | 141 -
src/backend/core/sandbox/workspace/desktop.py | 2 +-
.../core/sandbox/workspace/identity.py | 2 +-
.../workspace/resolution.py} | 14 +-
src/backend/core/services/__init__.py | 19 -
.../core/services/agent_market_categories.py | 77 -
.../core/services/agent_market_service.py | 940 ----
.../core/services/api_protocol_backfill.py | 94 -
src/backend/core/services/artifact_edition.py | 66 -
.../core/services/automation_tool_routing.py | 106 -
src/backend/core/services/chat_service.py | 1301 -----
.../core/services/commands/__init__.py | 1 -
.../core/services/desktop_cloud_bridge.py | 960 ----
.../core/services/edition_system_config.py | 6 -
.../core/services/edition_workspace.py | 52 -
src/backend/core/services/kb_edition.py | 12 -
src/backend/core/services/lark_service.py | 752 ---
src/backend/core/services/log_service.py | 649 ---
.../core/services/marketplace_service.py | 1400 -----
.../core/services/mcp_marketplace_service.py | 1772 -------
.../core/services/mcp_oauth_service.py | 529 --
.../core/services/mcp_tool_metadata.py | 19 -
.../core/services/mcp_tool_schema_backfill.py | 110 -
.../core/services/prompt_version_service.py | 889 ----
.../services/session_operations/admission.py | 262 -
.../core/services/skill_draft_service.py | 192 -
src/backend/core/services/system_config.py | 655 ---
.../core/services/tool_effect_ledger.py | 1808 -------
src/backend/core/services/user_agent_base.py | 854 ---
src/backend/core/spaces/edition_access.py | 17 -
src/backend/core/spaces/edition_revocation.py | 3 -
src/backend/core/vision/__init__.py | 45 -
src/backend/extensions/README.md | 2 +-
.../extensions/mcp_servers/_packaging.py | 6 +-
src/backend/extensions/mcp_servers/_serve.py | 28 +-
.../mcp_servers/agent_manager_mcp/impl.py | 48 +-
.../mcp_servers/agent_manager_mcp/server.py | 4 +-
.../mcp_servers/automation_task_mcp/impl.py | 18 +-
.../mcp_servers/automation_task_mcp/server.py | 16 +-
.../mcp_servers/browser_runtime_mcp/server.py | 8 +-
.../mcp_servers/internet_search_mcp/README.md | 4 +-
.../internet_search_mcp/_selftest.py | 2 +-
.../mcp_servers/internet_search_mcp/impl.py | 10 +-
.../internet_search_mcp/providers.py | 45 +
.../mcp_servers/internet_search_mcp/server.py | 35 +-
.../mcp_servers/management_registration.py | 8 +-
.../mcp_servers/plugin_manager_mcp/impl.py | 11 +-
.../mcp_servers/plugin_manager_mcp/server.py | 4 +-
.../retrieve_dataset_content_mcp/impl.py | 18 +-
.../local_impl.py | 25 +-
.../retrieve_dataset_content_mcp/server.py | 47 +-
.../retrieve_dataset_content_mcp/wiki_impl.py | 12 +-
.../mcp_servers/site_publish_mcp/server.py | 6 +-
.../mcp_servers/skill_manager_mcp/impl.py | 21 +-
.../mcp_servers/skill_manager_mcp/server.py | 4 +-
.../space_files_mcp/permissions.py | 8 +-
.../mcp_servers/space_files_mcp/server.py | 10 +-
.../mcp_servers/web_fetch_mcp/server.py | 4 +-
.../marketplace/agent-manager/mcp.json | 2 +-
.../plugins/marketplace/automation/mcp.json | 2 +-
.../marketplace/plugin-manager/mcp.json | 2 +-
.../plugin-creator/scripts/validate_plugin.py | 2 +-
.../plugins/marketplace/sites/mcp.json | 2 +-
.../marketplace/skill-manager/mcp.json | 2 +-
.../skill-creator/references/skill-anatomy.md | 2 +-
.../skill-creator/scripts/quick_validate.py | 2 +-
.../skills/marketplace/data-mask/config.yaml | 166 +-
src/backend/features/__init__.py | 157 +
src/backend/features/automation/README.md | 15 +
src/backend/features/automation/__init__.py | 1 +
src/backend/features/automation/admission.py | 80 +
.../features/automation/api/__init__.py | 1 +
.../automation/api}/automations.py | 27 +-
.../automation}/delivery_targets.py | 19 +-
.../automation/execution.py} | 4 +-
src/backend/features/automation/extension.py | 28 +
.../automation/loops.py} | 5 +-
.../automation/models.py} | 59 +-
.../automation/notifications.py} | 0
.../automation/remote_effect.py} | 3 +-
src/backend/features/automation/runs.py | 66 +
src/backend/features/automation/scheduling.py | 23 +
src/backend/features/automation/schema.py | 59 +
.../automation/service.py} | 155 +-
.../features/automation/tool_routing.py | 116 +
src/backend/features/desktop/README.md | 11 +
src/backend/features/desktop/__init__.py | 1 +
src/backend/features/desktop/api/__init__.py | 1 +
.../features/desktop/api/capability_auth.py | 48 +
.../features/desktop/api/capability_bridge.py | 92 +
.../desktop/api/capability_dependencies.py | 107 +
.../desktop/api/capability_manifests.py | 145 +
.../desktop/api/capability_mcp_gateway.py | 79 +
.../desktop/api/capability_model_gateway.py | 89 +
.../features/desktop/api/capability_tools.py | 192 +
.../desktop/api/capability_workcopies.py | 38 +
.../v1 => features/desktop/api}/desktop.py | 2 +-
.../desktop/api}/desktop_capabilities.py | 104 +-
.../desktop/api/desktop_capability.py | 12 +
.../desktop/api/desktop_capability_state.py | 52 +
.../desktop/api}/desktop_login.py | 2 +-
.../desktop/api}/desktop_observability.py | 8 +-
.../desktop/api}/device_link.py | 6 +-
.../v1 => features/desktop/api}/local.py | 20 +-
.../desktop/api}/local_file_actions.py | 4 +-
src/backend/features/desktop/auth/__init__.py | 1 +
.../desktop/auth/bridge.py} | 13 +-
.../desktop/auth/login_store.py} | 0
.../desktop/auth/ticket_store.py} | 0
.../features/desktop/bridge/__init__.py | 1 +
.../desktop/bridge/bundles.py} | 42 +-
src/backend/features/desktop/bridge/client.py | 214 +
.../features/desktop/bridge/client_state.py | 48 +
.../features/desktop/bridge/gateway.py | 225 +
.../features/desktop/bridge/identity.py | 254 +
.../desktop/bridge/managed_connectors.py | 144 +
.../desktop/bridge/model_credentials.py} | 25 +-
.../desktop/bridge/release_events.py} | 0
.../desktop/bridge/resource_plugins.py} | 19 +-
.../desktop/bridge/skills.py} | 47 +-
src/backend/features/desktop/bridge/status.py | 113 +
.../features/desktop/capabilities/__init__.py | 16 +
.../desktop}/capabilities/agents.py | 17 +-
.../desktop}/capabilities/change_merge.py | 10 +-
.../desktop}/capabilities/change_signal.py | 0
.../desktop}/capabilities/change_sync.py | 60 +-
.../desktop}/capabilities/connectors.py | 12 +-
.../desktop}/capabilities/credentials.py | 0
.../desktop}/capabilities/dependency.py | 28 +-
.../capabilities/dependency_declarations.py | 0
.../desktop}/capabilities/device_catalog.py | 90 +-
.../capabilities/device_plugin_catalog.py | 69 +-
.../desktop}/capabilities/discovery.py | 6 +-
.../desktop}/capabilities/invocation.py | 34 +-
.../capabilities/local_plugin_runtime.py | 21 +-
.../desktop}/capabilities/local_projection.py | 27 +-
.../capabilities/management_context.py | 17 +-
.../desktop}/capabilities/manifest_order.py | 2 +-
.../desktop}/capabilities/mcp_json.py | 8 +-
.../desktop}/capabilities/migration.py | 25 +-
.../desktop}/capabilities/paths.py | 31 +-
.../desktop}/capabilities/plugins.py | 47 +-
.../desktop}/capabilities/preparation.py | 28 +-
.../desktop}/capabilities/readiness.py | 24 +-
.../desktop}/capabilities/registry.py | 115 +-
.../desktop/capabilities/registry_types.py | 79 +
.../desktop}/capabilities/resolver.py | 4 +-
.../desktop/capabilities/runtime/__init__.py | 37 +
.../capabilities/runtime/connectors.py | 23 +-
.../capabilities/runtime/dependencies.py | 18 +-
.../capabilities/runtime/preparation.py | 25 +-
.../desktop}/capabilities/runtime/state.py | 52 +-
.../capabilities/session_availability.py | 0
.../desktop/capabilities/skill_account.py | 54 +
.../desktop/capabilities/skill_candidates.py | 140 +
.../desktop/capabilities/skill_hashes.py | 101 +
.../desktop/capabilities/skill_paths.py | 29 +
.../desktop/capabilities/skill_publication.py | 88 +
.../desktop/capabilities/skill_views.py | 62 +
.../features/desktop/capabilities/skills.py | 161 +
.../desktop/capabilities/skills_state.py | 35 +
.../desktop}/capabilities/store.py | 17 +-
.../desktop}/capabilities/view.py | 4 +-
.../desktop}/capabilities/warmup.py | 17 +-
.../features/desktop/capability/__init__.py | 1 +
.../desktop/capability/configs.py} | 6 +-
.../desktop/capability/credentials.py} | 2 +-
.../desktop/capability/entities.py} | 24 +-
.../desktop/capability/mcp.py} | 40 +-
.../desktop/capability/models.py} | 16 +-
.../desktop/capability/revision_provider.py | 22 +
.../desktop/capability/security.py} | 9 +-
.../desktop/capability/service.py} | 43 +-
.../desktop/capability/skills.py} | 18 +-
.../desktop/capability/sync_check.py} | 29 +-
.../desktop/capability/workcopies.py} | 45 +-
src/backend/features/desktop/edition.py | 23 +
src/backend/features/desktop/extension.py | 4 +
.../features/desktop/gateway/__init__.py | 1 +
.../desktop/gateway/observability.py} | 4 +-
.../features/desktop/local/__init__.py | 1 +
.../desktop/local/file_actions.py} | 2 +-
.../desktop/local/grants.py} | 21 +
.../desktop/local/management_migration.py} | 16 +-
.../desktop/local/manager_execution.py | 51 +
.../desktop/local/market_install.py} | 14 +-
.../desktop/local}/native_app_icons.py | 0
.../desktop/local/plugins}/__init__.py | 0
.../desktop/local/plugins}/assets.py | 10 +-
.../local/plugins}/legacy_migration.py | 27 +-
.../desktop/local/plugins}/service.py | 66 +-
.../desktop/local/plugins/sidecars.py | 33 +
.../desktop/local/publication_provider.py | 101 +
.../features/desktop/local/skills/__init__.py | 1 +
.../desktop/local/skills/editor.py} | 28 +-
.../desktop/local/skills/service.py} | 36 +-
.../desktop/local/skills/upload.py} | 34 +-
.../desktop/local/snapshots.py} | 0
.../desktop/models.py} | 69 +-
.../desktop/observability/__init__.py | 1 +
.../desktop/observability/context.py} | 8 +-
.../desktop/observability/delivery.py} | 11 +-
.../desktop/observability/fields.py} | 2 +-
.../desktop/observability/projection.py} | 8 +-
.../desktop/observability/sync.py} | 13 +-
.../desktop/plugin_resolution.py} | 39 +-
.../desktop/run_capabilities/__init__.py | 1 +
.../desktop/run_capabilities/preparation.py} | 22 +-
.../desktop/run_capabilities/provider.py | 118 +
.../desktop/skill_backend.py} | 20 +-
src/backend/features/evaluation/README.md | 7 +
src/backend/features/evaluation/__init__.py | 1 +
.../evaluation/admission.py} | 2 +-
.../features/evaluation/api/__init__.py | 1 +
.../evaluation/api}/evaluation_sandboxes.py | 2 +-
src/backend/features/evaluation/extension.py | 23 +
src/backend/features/evaluation/models.py | 1 +
src/backend/features/evaluation/policy.py | 27 +
.../features/evaluation/runtime/__init__.py | 1 +
.../evaluation/runtime/paths.py} | 0
.../evaluation/runtime/process_tools.py} | 7 +-
.../evaluation/runtime/runtime.py} | 0
.../evaluation/runtime/tools.py} | 59 +-
.../evaluation/sandbox_provider}/__init__.py | 0
.../evaluation/sandbox_provider}/binding.py | 0
.../sandbox_provider}/process_guard.py | 0
.../evaluation/sandbox_provider}/provider.py | 4 +-
.../evaluation/sandboxes.py} | 24 +-
src/backend/features/hosted_apps/README.md | 9 +
src/backend/features/hosted_apps/__init__.py | 1 +
.../features/hosted_apps/api/__init__.py | 1 +
.../api}/application_transports.py | 88 +-
.../hosted_apps/api}/applications.py | 65 +-
.../hosted_apps/collections.py} | 8 +-
.../hosted_apps/config.py} | 2 +-
.../hosted_apps/data.py} | 20 +-
.../hosted_apps/deployments.py} | 13 +-
.../hosted_apps/export.py} | 6 +-
src/backend/features/hosted_apps/extension.py | 17 +
.../hosted_apps/history.py} | 8 +-
.../hosted_apps/hosting_setup.py} | 4 +-
.../hosted_apps/industry.py} | 9 +-
.../hosted_apps/lifecycle.py} | 14 +-
.../hosted_apps/mcp.py} | 34 +-
src/backend/features/hosted_apps/models.py | 5 +
.../hosted_apps/operations.py} | 21 +-
.../hosted_apps/projects.py} | 10 +-
.../hosted_apps/publication.py} | 19 +-
.../hosted_apps/relational.py} | 2 +-
.../hosted_apps/resources/__init__.py | 1 +
.../hosted_apps/resources/admission.py | 159 +
.../features/hosted_apps/resources/cleanup.py | 71 +
.../hosted_apps/schema.py} | 0
.../hosted_apps/sql_policy.py} | 2 +-
.../hosted_apps/store.py} | 10 +-
src/backend/features/installed.py | 3 +
src/backend/features/knowledge/README.md | 11 +
.../kb => features/knowledge}/__init__.py | 2 +-
.../features/knowledge/api/__init__.py | 1 +
src/backend/features/knowledge/api/kb.py | 7 +
.../features/knowledge/api/kb_assets.py | 147 +
.../features/knowledge/api/kb_chunks.py | 175 +
.../features/knowledge/api/kb_documents.py | 254 +
.../features/knowledge/api/kb_helpers.py | 105 +
.../features/knowledge/api/kb_mutations.py | 117 +
.../features/knowledge/api/kb_spaces.py | 116 +
.../features/knowledge/api/kb_state.py | 8 +
.../v1 => features/knowledge/api}/kb_wiki.py | 24 +-
.../features/knowledge/asset_captions.py | 181 +
.../features/knowledge/asset_indexing.py | 88 +
.../features/knowledge/catalog_options.py | 7 +
.../knowledge/disabled_provider.py} | 4 +-
.../knowledge/disabled_retrieval.py} | 0
.../knowledge}/document_status.py | 0
src/backend/features/knowledge/edition.py | 33 +
src/backend/features/knowledge/enrichment.py | 126 +
src/backend/features/knowledge/extension.py | 19 +
.../kb => features/knowledge}/index_queue.py | 4 +-
.../kb => features/knowledge}/index_worker.py | 8 +-
src/backend/features/knowledge/kb_assets.py | 231 +
.../features/knowledge/kb_assets_state.py | 31 +
src/backend/features/knowledge/kb_parser.py | 116 +
.../features/knowledge/kb_parser_state.py | 34 +
src/backend/features/knowledge/kb_vector.py | 149 +
.../features/knowledge/kb_vector_state.py | 27 +
src/backend/features/knowledge/models.py | 377 ++
.../features/knowledge/parser_chunks.py | 278 +
.../features/knowledge/parser_extract.py | 302 ++
.../features/knowledge/parser_semantic.py | 130 +
.../features/knowledge/parser_tokens.py | 26 +
.../features/knowledge/parser_types.py | 43 +
.../knowledge/personal_permissions.py} | 2 +-
.../knowledge/personal_request_models.py} | 0
.../knowledge/processing.py} | 193 +-
.../knowledge/repository.py} | 4 +-
.../knowledge/service.py} | 34 +-
.../features/knowledge/vector_client.py | 166 +
.../features/knowledge/vector_fields.py | 16 +
.../features/knowledge/vector_indexing.py | 204 +
.../knowledge}/wiki/__init__.py | 2 +-
.../kb => features/knowledge}/wiki/cite.py | 10 +-
.../kb => features/knowledge}/wiki/config.py | 2 +-
.../kb => features/knowledge}/wiki/dedup.py | 16 +-
.../kb => features/knowledge}/wiki/extract.py | 12 +-
.../knowledge}/wiki/finalize.py | 17 +-
.../kb => features/knowledge}/wiki/jobs.py | 4 +-
.../kb => features/knowledge}/wiki/linkify.py | 0
.../kb => features/knowledge}/wiki/llm.py | 10 +-
.../features/knowledge/wiki/local_graph.py | 156 +
.../knowledge/wiki/local_projection.py | 51 +
.../features/knowledge/wiki/local_provider.py | 271 +
.../knowledge/wiki/local_provider_state.py | 15 +
.../knowledge}/wiki/pipeline.py | 26 +-
.../kb => features/knowledge}/wiki/prompts.py | 0
.../kb => features/knowledge}/wiki/reduce.py | 17 +-
.../kb => features/knowledge}/wiki/slug.py | 0
.../kb => features/knowledge}/wiki/store.py | 8 +-
.../knowledge}/wiki/taxonomy.py | 14 +-
.../kb => features/knowledge}/wiki/worker.py | 12 +-
.../kb => features/knowledge}/wiki_router.py | 16 +-
src/backend/features/marketplace/README.md | 15 +
src/backend/features/marketplace/__init__.py | 1 +
.../features/marketplace/agent_catalog.py | 119 +
.../features/marketplace/agent_categories.py | 38 +
.../marketplace/agent_installation.py | 240 +
.../features/marketplace/agent_metadata.py | 101 +
.../features/marketplace/agent_publishing.py | 165 +
.../marketplace/agent_submission_data.py | 155 +
.../features/marketplace/agent_submissions.py | 160 +
.../marketplace/agent_versions.py} | 31 +-
.../features/marketplace/agents_state.py | 14 +
.../features/marketplace/api/__init__.py | 1 +
.../marketplace/api}/agent_marketplace.py | 28 +-
.../marketplace/api}/catalog_ownership.py | 9 +-
.../marketplace/api}/marketplace.py | 27 +-
.../marketplace/api}/mcp_marketplace.py | 59 +-
.../features/marketplace/core_catalog.py | 108 +
src/backend/features/marketplace/edition.py | 10 +
src/backend/features/marketplace/extension.py | 4 +
.../marketplace/mcp_administration.py | 291 ++
.../marketplace/mcp_catalog.py} | 0
.../features/marketplace/mcp_credentials.py | 130 +
.../features/marketplace/mcp_curated.py | 127 +
.../features/marketplace/mcp_installation.py | 267 +
.../features/marketplace/mcp_listing.py | 142 +
.../marketplace/mcp_monitor.py} | 41 +-
.../features/marketplace/mcp_publishing.py | 193 +
.../features/marketplace/mcp_revalidation.py | 146 +
src/backend/features/marketplace/mcp_state.py | 17 +
.../features/marketplace/mcp_submissions.py | 319 ++
.../features/marketplace/mcp_validation.py | 166 +
.../features/marketplace/mcp_versions.py | 221 +
src/backend/features/marketplace/models.py | 388 ++
.../features/marketplace/package_source.py | 35 +
.../marketplace/personal_listing.py} | 2 +-
.../marketplace/personal_visibility.py} | 0
.../marketplace/plugin_market.py} | 63 +-
.../features/marketplace/skill_archives.py | 49 +
.../features/marketplace/skill_catalog.py | 184 +
.../marketplace/skill_installation.py | 199 +
.../features/marketplace/skill_metadata.py | 174 +
.../features/marketplace/skill_publishing.py | 231 +
.../marketplace/skill_submission_data.py | 80 +
.../features/marketplace/skill_submissions.py | 190 +
.../marketplace/skill_versions.py} | 70 +-
.../features/marketplace/skills_state.py | 56 +
.../features/marketplace/versions/__init__.py | 1 +
.../marketplace/versions/archive.py} | 0
.../marketplace/versions/config.py} | 4 +-
.../marketplace/versions/sources.py} | 50 +-
.../marketplace/versions/store.py} | 37 +-
src/backend/features/sites/README.md | 11 +
src/backend/features/sites/__init__.py | 1 +
src/backend/features/sites/api/__init__.py | 1 +
.../sites/api}/internal_site_auth.py | 0
.../sites/api}/internal_sites.py | 39 +-
.../sites/api}/local_site_sources.py | 3 +-
.../sites/api}/site_gate.py | 0
.../routes/v1 => features/sites/api}/sites.py | 11 +-
.../sites/api}/sites_serve.py | 71 +-
.../sites/builtin_upgrade.py} | 29 +-
.../sites/desktop_publish.py} | 64 +-
src/backend/features/sites/edition.py | 10 +
src/backend/features/sites/extension.py | 34 +
.../sites/listing.py} | 8 +-
.../sites/local_sources.py} | 28 +-
.../sites/management.py} | 14 +-
.../site.py => features/sites/models.py} | 2 +-
.../sites/packaging.py} | 17 +-
.../sites/password.py} | 0
.../sites/personal_access.py} | 2 +-
.../sites/personal_scope.py} | 0
.../sites/rate_limit.py} | 0
.../site.py => features/sites/repository.py} | 2 +-
.../sites/service.py} | 17 +-
src/backend/features/sites/tools/__init__.py | 1 +
.../sites/tools/design_picker.py} | 46 +-
src/backend/features/workbench/README.md | 9 +
.../workbench}/__init__.py | 0
.../workbench}/activity.py | 6 +-
.../features/workbench/api/__init__.py | 1 +
.../workbench/api}/plugin_ui.py | 16 +-
.../workbench/api}/workbench.py | 67 +-
.../workbench/api}/workbench_activity.py | 38 +-
.../api}/workbench_application_config.py | 14 +-
.../workbench/api}/workbench_auth.py | 21 +-
.../workbench/api}/workbench_capabilities.py | 104 +-
.../workbench/api}/workbench_diagnostics.py | 6 +-
.../workbench/api}/workbench_events.py | 9 +-
.../workbench/api}/workbench_preferences.py | 2 +-
.../workbench/api}/workbench_resources.py | 47 +-
.../api}/workbench_revision_leases.py | 2 +-
.../workbench/api}/workbench_state.py | 2 +-
.../workbench/api}/workbench_upgrades.py | 2 +-
.../workbench/api}/workbench_workspace.py | 12 +-
.../workbench}/application_config.py | 14 +-
.../workbench}/application_config_bindings.py | 26 +-
.../workbench}/background.py | 124 +-
.../workbench}/capabilities.py | 14 +-
.../workbench}/catalog.py | 6 +-
.../workbench}/coordination.py | 0
.../workbench}/diagnostic_probe.py | 2 +-
.../workbench}/diagnostic_resources.py | 0
.../workbench}/diagnostics.py | 2 +-
.../workbench}/directory_revision.py | 2 +-
src/backend/features/workbench/edition.py | 10 +
src/backend/features/workbench/extension.py | 4 +
.../workbench}/external.py | 12 +-
.../workbench}/external_auth.py | 4 +-
.../workbench}/external_boundary.py | 2 +-
.../workbench}/external_embed.py | 2 +-
.../workbench}/installation_binding.py | 24 +-
.../workbench}/local_plugin_upgrades.py | 32 +-
.../workbench}/migration.py | 14 +-
.../workbench}/migration_compat.py | 25 +-
.../workbench}/migration_plan.py | 12 +-
.../workbench}/migration_snapshot.py | 10 +-
src/backend/features/workbench/models.py | 1 +
.../workbench}/origin.py | 0
.../features/workbench/package_source.py | 19 +
.../workbench}/plugin_candidate_storage.py | 33 +-
.../workbench}/plugin_catalog.py | 10 +-
.../workbench}/plugin_revisions.py | 36 +-
.../workbench}/plugin_upgrades.py | 26 +-
.../workbench}/policy.py | 6 +-
.../workbench}/preferences.py | 4 +-
.../workbench}/protocol.py | 0
.../features/workbench/resource_deadlines.py | 122 +
.../features/workbench/resource_owner.py | 65 +
.../workbench}/revocation.py | 54 +-
.../workbench}/revocation_sweep.py | 30 +-
.../schemas/application.schema.json | 0
.../workbench}/schemas/host.json | 0
.../workbench}/schemas/module.schema.json | 0
.../workbench}/state.py | 8 +-
.../workbench}/state_recovery.py | 0
.../workbench}/ui/__init__.py | 0
.../workbench}/ui/contract.py | 2 +-
.../workbench}/ui/contributions.py | 20 +-
.../workbench}/ui/data_proxy.py | 12 +-
.../workbench}/ui/declarations.py | 2 +-
.../workbench}/ui/primitives.py | 2 +-
.../workbench}/ui/workspace_enabled.py | 0
.../workbench}/workspace_page.py | 4 +-
src/backend/hugagent_cli/main.py | 43 +-
src/backend/hugagent_cli/schema.py | 20 +
src/backend/hugagent_cli/transport.py | 32 +-
src/backend/integrations/__init__.py | 8 +
src/backend/integrations/api/__init__.py | 1 +
.../v1 => integrations/api}/integrations.py | 49 +-
src/backend/integrations/channels/README.md | 9 +
.../channels/__init__.py | 9 +-
.../channels/adapters/__init__.py | 0
.../channels/adapters/dingtalk.py | 325 ++
.../channels/adapters/dingtalk_delivery.py | 213 +
.../channels/adapters/dingtalk_history.py | 361 ++
.../channels/adapters/dingtalk_settings.py | 3 +
.../integrations/channels/adapters/lark.py | 261 +
.../channels/adapters/lark_delivery.py | 152 +
.../channels/adapters/lark_messages.py | 317 ++
.../channels/adapters/lark_settings.py | 3 +
.../channels/adapters/wecom.py | 2 +-
.../channels/adapters/weixin.py | 2 +-
.../integrations/channels/api/__init__.py | 1 +
.../channels/api}/channel_desktop.py | 39 +-
.../channels/api}/channels.py | 46 +-
.../channels/desktop_worker.py} | 110 +-
.../integrations/channels/extension.py | 7 +
src/backend/integrations/channels/inbound.py | 368 ++
.../channels/inbound_attachments.py | 82 +
.../integrations/channels/inbound_delivery.py | 118 +
.../integrations/channels/inbound_history.py | 157 +
.../channels/inbound_observation.py | 168 +
.../integrations/channels/inbound_sessions.py | 131 +
.../integrations/channels/inbound_state.py | 46 +
.../channels/manager.py | 14 +-
.../channels/markdown.py | 0
src/backend/integrations/channels/models.py | 133 +
.../channels/outbound.py | 72 +-
.../channels/protocol.py | 0
.../channels/registry.py | 10 +-
.../channels/relay.py} | 4 +-
.../channels/relay_dispatch.py} | 98 +-
.../channels/relay_rpc.py} | 113 +-
.../channels/repository.py} | 2 +-
.../channels/service.py} | 242 +-
.../integrations/channels/tools/__init__.py | 1 +
.../channels/tools/attachment.py} | 163 +-
src/backend/integrations/dingtalk/README.md | 5 +
src/backend/integrations/dingtalk/__init__.py | 1 +
.../integrations/dingtalk/cube_credentials.py | 129 +
.../integrations/dingtalk/environment.py | 29 +
.../integrations/dingtalk/extension.py | 10 +
src/backend/integrations/dingtalk/models.py | 61 +
src/backend/integrations/dingtalk/mounts.py | 77 +
src/backend/integrations/dingtalk/parsing.py | 105 +
.../integrations/dingtalk/repository.py | 39 +
src/backend/integrations/dingtalk/sandbox.py | 15 +
.../dingtalk/service.py} | 164 +-
src/backend/integrations/dingtalk/storage.py | 34 +
src/backend/integrations/email/README.md | 5 +
src/backend/integrations/email/__init__.py | 1 +
.../integrations/email/cube_credentials.py | 53 +
src/backend/integrations/email/extension.py | 10 +
src/backend/integrations/email/models.py | 65 +
src/backend/integrations/email/mounts.py | 72 +
src/backend/integrations/email/repository.py | 37 +
src/backend/integrations/email/sandbox.py | 14 +
.../email/service.py} | 65 +-
src/backend/integrations/email/storage.py | 36 +
src/backend/integrations/lark/README.md | 5 +
src/backend/integrations/lark/__init__.py | 1 +
.../integrations/lark/configuration.py | 200 +
.../integrations/lark/cube_credentials.py | 90 +
src/backend/integrations/lark/extension.py | 10 +
src/backend/integrations/lark/login.py | 114 +
src/backend/integrations/lark/models.py | 67 +
src/backend/integrations/lark/mounts.py | 75 +
src/backend/integrations/lark/parsing.py | 141 +
src/backend/integrations/lark/process.py | 59 +
src/backend/integrations/lark/repository.py | 37 +
src/backend/integrations/lark/sandbox.py | 14 +
src/backend/integrations/lark/service.py | 189 +
src/backend/integrations/lark/state.py | 49 +
src/backend/integrations/lark/storage.py | 51 +
src/backend/integrations/yida/README.md | 5 +
src/backend/integrations/yida/__init__.py | 1 +
.../integrations/yida/cube_credentials.py | 152 +
.../yida/execution.py} | 4 +-
src/backend/integrations/yida/extension.py | 13 +
src/backend/integrations/yida/models.py | 1 +
src/backend/integrations/yida/mounts.py | 68 +
src/backend/integrations/yida/sandbox.py | 18 +
.../yida/service.py} | 6 +-
.../integrations/yida/skill_content.py | 12 +
src/backend/integrations/yida/storage.py | 52 +
src/backend/orchestration/__init__.py | 5 +-
.../orchestration/automations/__init__.py | 0
.../automation_artifacts.py | 3 +-
.../automations/automation_conversation.py | 396 ++
.../automations/automation_delivery.py | 62 +
.../automations/automation_dispatch.py | 306 ++
.../automations/automation_lease.py | 113 +
.../automations/automation_recovery.py | 39 +
.../automations/automation_scheduler.py | 136 +
.../orchestration/chat_run_executor.py | 4651 -----------------
src/backend/orchestration/citation_anchor.py | 551 --
.../orchestration/commands/__init__.py | 1 -
src/backend/orchestration/commands/runtime.py | 262 -
src/backend/orchestration/configuration.py | 6 +
.../orchestration/local_plugin_sidecars.py | 75 -
src/backend/orchestration/loops/__init__.py | 0
src/backend/orchestration/loops/admission.py | 122 +
.../{autonomous_loop.py => loops/driver.py} | 26 +-
.../{loop_planner.py => loops/planner.py} | 14 +-
.../orchestration/loops/presentation.py | 71 +
src/backend/orchestration/loops/recovery.py | 93 +
.../requirements.py} | 59 +-
.../loop_reviewer.py => loops/reviewer.py} | 161 +-
src/backend/orchestration/loops/run.py | 509 ++
.../orchestration/memory_integration.py | 576 --
src/backend/orchestration/pending.py | 36 +
src/backend/orchestration/plans/__init__.py | 0
src/backend/orchestration/plans/admission.py | 171 +
.../orchestration/plans/execute_run.py | 338 ++
.../orchestration/plans/generate_run.py | 278 +
.../{subagents/plan_mode.py => plans/mode.py} | 272 +-
.../schedulers/automation_conversation.py | 307 --
.../schedulers/automation_dispatch.py | 279 -
.../schedulers/automation_lease.py | 58 -
.../schedulers/automation_recovery.py | 45 -
.../schedulers/automation_scheduler.py | 330 --
src/backend/orchestration/streaming.py | 999 ----
.../orchestration/subagents/__init__.py | 2 -
.../subagents/ontology_reviewer.py | 656 ---
.../{schedulers => workers}/__init__.py | 0
.../{schedulers => workers}/_worker_base.py | 12 +-
.../workers}/edition_startup.py | 0
.../evolution_scheduler.py | 4 +-
.../memory_ttl_scheduler.py | 6 +-
.../persona_distill_worker.py | 6 +-
.../orchestration/workers/run_recovery.py | 37 +
src/backend/orchestration/workflow.py | 3463 ------------
src/backend/personal_agent/README.md | 2 +-
src/backend/personal_agent/access.py | 2 +-
.../personal_agent/api/conversation.py | 2 +-
src/backend/personal_agent/api/sessions.py | 11 +-
src/backend/personal_agent/background.py | 8 +-
.../personal_agent/channels/channel_bridge.py | 64 +-
.../personal_agent/computer/computer.py | 5 +-
.../personal_agent/computer/devices.py | 45 +-
.../personal_agent/computer/driver_install.py | 2 +-
.../personal_agent/computer/live_stream.py | 27 +-
.../personal_agent/computer/sandbox.py | 42 +-
.../computer/screenshot_pruning.py | 72 +-
.../conversation/conversations.py | 3 +-
.../personal_agent/conversation/front.py | 41 +-
.../personal_agent/delivery/delivery.py | 4 +-
.../personal_agent/device_link/README.md | 3 +
.../device_link/__init__.py | 2 +-
.../device_link/driver.py | 0
.../device_link/local_api.py | 0
.../device_link/runner.py | 6 +-
.../device_link/store.py | 0
src/backend/personal_agent/extension.py | 26 +-
.../personal_agent/identity/capabilities.py | 8 +-
src/backend/personal_agent/identity/config.py | 2 +-
.../personal_agent/identity/service.py | 2 +-
.../personal_agent/scheduling/schedules.py | 2 +-
.../personal_agent/sessions/session_auth.py | 43 +-
.../personal_agent/sessions/session_cloud.py | 89 +-
.../sessions/session_manager.py | 151 +-
.../personal_agent/sessions/session_tool.py | 37 +-
.../personal_agent/sessions/session_watch.py | 2 +-
.../personal_agent/tasks/attachments.py | 10 +-
src/backend/personal_agent/tasks/runtime.py | 90 +-
src/backend/personal_agent/tasks/service.py | 2 +-
src/backend/personal_agent/tasks/steering.py | 6 +-
.../personal_agent/tasks/task_threads.py | 6 +-
src/backend/personal_agent/tasks/worker.py | 182 +-
src/backend/personal_agent/tools/email.py | 5 +-
.../personal_agent/tools/front_tools.py | 52 +-
src/backend/personal_agent/tools/responses.py | 4 +-
src/backend/personal_agent/tools/tools.py | 49 +-
.../personal_agent/workspace/dream_plan.py | 4 +-
.../personal_agent/workspace/dreaming.py | 8 +-
.../personal_agent/workspace/prompting.py | 7 +-
src/backend/prompts/desktop_templates.py | 5 +-
src/backend/prompts/desktop_workspace.py | 6 +-
src/backend/prompts/kb_lite_section.py | 4 +-
src/backend/prompts/project_section.py | 24 +-
src/backend/prompts/prompt_runtime.py | 20 +-
src/backend/prompts/system_builder.py | 19 +-
src/backend/scripts/_loop_convergence_unit.py | 147 -
src/backend/scripts/_loop_db_resume_unit.py | 92 -
.../scripts/_loop_evaluator_route_unit.py | 88 -
src/backend/scripts/_loop_integration_test.py | 88 -
src/backend/scripts/_loop_llmeval_smoke.py | 48 -
src/backend/scripts/_loop_preflight.py | 73 -
src/backend/scripts/_loop_selfverify_smoke.py | 66 -
src/backend/scripts/_loop_smoke.py | 90 -
src/backend/scripts/_loop_test_edgebench.py | 99 -
src/backend/scripts/backend_entrypoint.sh | 4 +-
src/backend/scripts/desktop_cloud_wrapper.py | 76 -
src/backend/scripts/desktop_fixture_mcp.py | 50 -
src/backend/scripts/dual_mode_e2e.py | 947 ----
src/backend/scripts/edgebench/ann_judge.py | 136 -
.../scripts/edgebench/perm_opt_judge.py | 88 -
src/backend/scripts/edit_chat_message.py | 198 -
src/backend/scripts/evaluation_sandbox_e2e.py | 89 -
src/backend/scripts/export_content.py | 317 --
.../scripts/harness_fault_injection.py | 637 ---
src/backend/scripts/import_cherry_agents.py | 372 --
src/backend/scripts/import_content.py | 379 --
.../scripts/run_evolution_benchmark.py | 308 --
src/backend/scripts/run_evolution_llm_ab.py | 621 ---
src/backend/scripts/workbench_migration.py | 66 -
src/backend/tests/_capability_index.py | 11 +-
.../tests/api/api_routes_v1_selftest.py | 2 +-
src/backend/tests/api/catalog_api_selftest.py | 9 +-
src/backend/tests/api/chat_fork_fixtures.py | 6 +-
src/backend/tests/api/test_agent_api_keys.py | 9 +-
.../api/test_agent_response_admission.py | 100 +-
.../tests/api/test_agent_response_scope.py | 24 +-
src/backend/tests/api/test_agent_responses.py | 14 +-
.../test_api_key_run_lifecycle_postgres.py | 6 +-
src/backend/tests/api/test_archive_files.py | 6 +-
src/backend/tests/api/test_archive_safety.py | 4 +-
.../tests/api/test_artifacts_backfill_once.py | 4 +-
.../tests/api/test_cancel_continue_e2e.py | 34 +-
.../tests/api/test_catalog_event_loop.py | 27 +-
.../api/test_chat_connector_selection.py | 4 +-
.../api/test_chat_event_loop_performance.py | 8 +-
.../tests/api/test_chat_fork_derived_data.py | 5 +-
src/backend/tests/api/test_chat_forks.py | 13 +-
.../tests/api/test_chat_reference_api.py | 4 +-
.../tests/api/test_chat_rerun_options.py | 22 +-
.../api/test_chat_rerun_start_failure.py | 10 +-
.../tests/api/test_chat_run_steer_api.py | 9 +-
.../tests/api/test_chat_stream_sequencer.py | 61 +-
.../tests/api/test_chat_thinking_mode.py | 4 +-
.../tests/api/test_cross_turn_file_access.py | 10 +-
.../tests/api/test_deep_folder_repair.py | 2 +-
.../tests/api/test_evaluation_sandboxes.py | 2 +-
src/backend/tests/api/test_file_location.py | 2 +-
.../tests/api/test_file_open_browser_e2e.py | 4 +-
.../tests/api/test_loop_cancel_scope.py | 41 +
src/backend/tests/api/test_me_system.py | 2 +-
.../api/test_model_reasoning_settings.py | 2 +
.../tests/api/test_page_config_backfill.py | 6 +-
.../tests/api/test_plugin_delete_isolation.py | 4 +-
.../tests/api/test_project_environments.py | 7 +-
.../tests/api/test_project_local_resolve.py | 157 +
src/backend/tests/api/test_public_chats.py | 42 +-
.../auth/test_auth_dependencies_off_loop.py | 176 +
.../tests/auth/test_bearer_identity.py | 180 +
.../tests/browser_automation_server.py | 81 +-
.../test_credential_continuity.py | 189 -
.../ce_release/test_ce_api_key_lifecycle.py | 8 +-
.../ce_release/test_ce_history_display.py | 2 +-
.../ce_release/test_ce_release_regressions.py | 159 +-
.../tests/ce_release/test_ce_tool_media.py | 8 +-
.../test_desktop_capability_wiring.py | 6 +-
.../ce_release/test_personal_agent_ce.py | 12 +-
.../test_workbench_migration_snapshot.py | 6 +-
src/backend/tests/chat/test_display_bounds.py | 2 +-
.../tests/chat/test_manual_compaction_api.py | 234 +
.../tests/chat/test_mcp_result_payload.py | 78 +
.../tests/chat/test_message_length_limits.py | 52 +
.../tests/chat/test_quoted_follow_up.py | 93 +
src/backend/tests/chat/test_sidebar_order.py | 14 +-
.../chat/test_snapshot_transaction_cleanup.py | 23 +
.../tests/chat/test_user_questions_api.py | 35 +-
src/backend/tests/conftest.py | 48 +-
.../tests/content/artifacts_selftest.py | 4 +-
src/backend/tests/content/catalog_selftest.py | 5 +-
.../tests/content/content_blocks_selftest.py | 2 +-
.../tests/content/security_selftest.py | 28 +-
.../content/test_artifact_default_path.py | 24 +
.../content/test_artifact_record_store.py | 4 +-
.../content/test_artifact_storage_keys.py | 10 +-
src/backend/tests/content/test_extra_files.py | 42 +-
.../tests/content/test_file_preview.py | 72 +-
.../tests/content/test_local_file_location.py | 6 +-
.../content/test_office_preview_cache.py | 6 +-
.../test_storage_existence_is_strict.py | 6 +-
.../runs/test_automation_journal_guard.py | 89 +
src/backend/tests/db_support.py | 43 +
.../tests/e2e/_e2e_regression_smoke.py | 2 +-
src/backend/tests/e2e/agentscope_poc.py | 2 +-
src/backend/tests/e2e/core_external_server.py | 4 +-
src/backend/tests/e2e/core_refactor_server.py | 2 +-
src/backend/tests/e2e/model_tests.py | 4 +-
src/backend/tests/evolution/conftest.py | 2 +-
.../test_benchmark_and_activation.py | 24 +-
.../evolution/test_candidate_reasoning.py | 32 +-
.../evolution/test_cassette_and_replay.py | 4 +-
.../tests/evolution/test_compiler_v2.py | 73 +-
.../evolution/test_console_and_metrics.py | 8 +-
.../tests/evolution/test_credit_assigner.py | 2 +-
.../evolution/test_engines_end_to_end.py | 71 +-
.../tests/evolution/test_episode_assembler.py | 32 +-
.../test_experiments_and_federation.py | 4 +-
.../tests/evolution/test_exposure_and_ramp.py | 9 +-
.../evolution/test_fault_injection_gate.py | 4 +-
.../tests/evolution/test_mechanisms.py | 6 +-
.../tests/evolution/test_ontology_bridge.py | 2 +-
.../evolution/test_outcomes_and_backfill.py | 6 +-
.../tests/evolution/test_personal_approval.py | 61 +-
.../test_personal_cycle_and_prefs.py | 20 +-
.../evolution/test_policies_and_memory_ops.py | 67 +-
.../tests/evolution/test_promotion_chain.py | 98 +-
.../evolution/test_react_intervention.py | 29 +-
.../tests/evolution/test_readiness_gate.py | 18 +-
.../tests/evolution/test_realistic_corpus.py | 46 +-
.../evolution/test_release_state_machine.py | 2 +-
.../tests/evolution/test_runtime_binding.py | 8 +-
.../tests/evolution/test_settlement.py | 2 +-
.../tests/evolution/test_settlement_runner.py | 4 +-
.../evolution/test_skill_selection_record.py | 6 +-
src/backend/tests/evolution/test_sop_judge.py | 2 +-
.../tests/evolution/test_three_engines.py | 59 +-
src/backend/tests/evolution/test_trigger.py | 9 +-
.../tests/evolution/test_turn_card_edits.py | 4 +-
.../tests/evolution/test_user_contribution.py | 72 +-
src/backend/tests/features/__init__.py | 1 +
.../tests/features/automation/__init__.py | 1 +
.../tests/features/automation/run_fixtures.py | 15 +
.../test_automation_delivery_deadline.py | 179 +
.../test_automation_execution_location.py | 94 +-
.../automation/test_automation_lease_close.py | 199 +
.../test_automation_manual_trigger.py | 81 +
.../test_automation_notifications.py | 2 +-
.../test_automation_oneshot_finalize.py | 49 +
.../test_automation_ownership_postgres.py | 173 +
.../test_automation_remote_effect.py | 64 +-
.../test_automation_run_ownership.py | 256 +
.../test_delivery_target_migration.py | 31 +
.../automation/test_durable_admission.py | 96 +
.../automation}/test_loop_service_harness.py | 2 +-
.../automation/test_queue_postgres.py | 94 +
.../tests/features/desktop/__init__.py | 1 +
.../features/desktop/capabilities/__init__.py | 1 +
.../desktop}/capabilities/_cloud_identity.py | 0
.../desktop}/capabilities/conftest.py | 36 +-
.../capabilities/management_browser_server.py | 17 +-
.../capabilities/runtime_recovery_support.py | 19 +-
.../test_account_authorization.py | 30 +-
.../capabilities/test_agent_rerun_identity.py | 83 +-
.../capabilities/test_agent_runtime_grants.py | 22 +-
.../capabilities/test_agents_plugins.py | 42 +-
.../test_autonomous_loop_scopes.py | 95 +-
.../capabilities/test_available_runtime.py | 65 +-
.../test_capability_change_sync.py | 56 +-
.../test_catalog_cloud_only_in_hybrid.py | 41 +-
.../test_catalog_plugin_components_hidden.py | 8 +-
.../capabilities/test_change_merge.py | 4 +-
.../capabilities/test_change_signal.py | 2 +-
.../capabilities/test_cloud_identity.py | 2 +-
.../capabilities/test_cloud_management.py | 17 +-
.../test_cloud_parallel_preparation.py | 33 +-
.../test_cloud_plugin_binding_runtime.py | 32 +-
.../capabilities/test_cloud_sticky_plugins.py | 52 +-
.../capabilities/test_cloud_workcopies.py | 25 +-
.../desktop}/capabilities/test_core_layers.py | 13 +-
.../test_credential_continuity.py | 193 +
.../capabilities/test_dependency_closure.py | 53 +-
.../test_desktop_capabilities_api.py | 52 +-
.../test_desktop_content_safety.py | 67 +-
.../test_desktop_declaration_export.py | 21 +-
.../test_desktop_model_reasoning_safety.py | 21 +-
.../test_desktop_resource_plugins.py | 28 +-
.../test_desktop_token_security.py | 36 +-
.../capabilities/test_desktop_warmup.py | 28 +-
.../capabilities/test_device_api_contract.py | 58 +-
.../test_device_catalog_projection.py | 34 +-
.../test_dynamic_model_epoch_cache.py | 28 +-
.../test_explicit_desktop_invocation.py | 27 +-
.../test_gateway_invocation_proof.py | 16 +-
.../test_gateway_unknown_outcome.py | 52 +-
.../capabilities/test_hybrid_owned_plugins.py | 22 +-
.../test_initial_sync_readiness.py | 30 +-
.../test_junction_windows_paths.py | 10 +-
.../capabilities/test_loader_store_mode.py | 34 +-
.../test_local_folder_discovery.py | 4 +-
.../test_local_management_migration.py | 22 +-
.../test_local_skill_projection.py | 17 +-
.../capabilities/test_management_readiness.py | 26 +-
.../capabilities/test_management_tools.py | 61 +-
.../capabilities/test_manager_lifecycle.py | 8 +-
.../capabilities/test_manager_regressions.py | 29 +-
.../capabilities/test_manifest_order.py | 32 +-
.../capabilities/test_manifest_tool_schema.py | 27 +-
.../desktop}/capabilities/test_mcp_json.py | 2 +-
.../capabilities/test_mcp_json_consistency.py | 2 +-
.../capabilities/test_memory_epoch_cache.py | 31 +-
.../desktop}/capabilities/test_migration.py | 9 +-
.../test_migration_preservation.py | 5 +-
.../test_model_credential_reference.py | 13 +-
.../test_model_error_diagnostics.py | 6 +-
.../test_owned_connector_assembly.py | 38 +-
.../test_partial_session_availability.py | 33 +-
.../capabilities/test_plugin_connections.py | 12 +-
.../capabilities/test_plugin_device_assets.py | 10 +-
.../test_prepare_lock_not_held_over_io.py | 2 +-
.../capabilities/test_progressive_plugins.py | 166 +-
.../test_runtime_frozen_bindings.py | 142 +-
.../test_runtime_publication_recovery.py | 120 +-
.../capabilities/test_runtime_recovery.py | 72 +-
.../test_runtime_scope_consumers.py | 40 +-
.../capabilities/test_runtime_scopes.py | 104 +-
.../test_runtime_selection_recovery.py | 99 +-
.../test_runtime_session_recovery.py | 103 +-
.../test_selection_dependency_sources.py | 36 +-
.../capabilities/test_selection_readiness.py | 14 +-
.../capabilities/test_store_file_integrity.py | 19 +-
.../test_toolkit_runtime_alias.py | 40 +-
.../capabilities/test_user_facing_errors.py | 11 +-
.../capabilities/test_view_os_errors.py | 7 +-
.../test_workbench_local_upgrades.py | 16 +-
.../desktop}/desktop_login_browser_server.py | 6 +-
.../tests/features/desktop/index_fixtures.py | 13 +
.../features/desktop/observability_cases_1.py | 312 ++
.../features/desktop/observability_cases_2.py | 270 +
.../features/desktop/observability_support.py | 32 +
.../desktop}/run_desktop_browser_e2e.py | 2 +-
.../desktop}/test_desktop_auth.py | 4 +-
.../desktop}/test_desktop_bridge.py | 26 +-
.../desktop}/test_desktop_capability.py | 101 +-
.../test_desktop_capability_models.py | 27 +-
.../desktop}/test_desktop_cloud_skills.py | 70 +-
.../desktop}/test_desktop_device_login.py | 9 +-
.../test_desktop_memory_persistence.py | 8 +-
.../desktop}/test_desktop_model_gateway.py | 45 +-
.../desktop/test_desktop_observability.py | 20 +
...op_observability_authenticated_delivery.py | 54 +-
.../test_desktop_observability_delivery.py | 64 +-
.../desktop}/test_desktop_release_events.py | 4 +-
.../desktop}/test_desktop_sync_locking.py | 34 +-
.../desktop}/test_desktop_updates.py | 4 +-
.../desktop}/test_local_file_actions.py | 20 +-
.../desktop}/test_local_grant_service.py | 2 +-
.../desktop}/test_local_snapshot_service.py | 2 +-
.../tests/features/evaluation/__init__.py | 1 +
.../tests/features/evaluation/conftest.py | 9 +
.../evaluation}/test_evaluation_admission.py | 4 +-
.../evaluation}/test_evaluation_binding.py | 2 +-
.../evaluation}/test_evaluation_factory.py | 15 +-
.../test_evaluation_process_guard.py | 2 +-
.../evaluation}/test_evaluation_provider.py | 10 +-
.../evaluation}/test_evaluation_router.py | 38 +-
.../evaluation}/test_evaluation_runtime.py | 36 +-
.../evaluation}/test_evaluation_service.py | 7 +-
.../tests/features/hosted_apps/__init__.py | 1 +
.../application_hosting_support.py | 21 +-
.../test_application_agent_contract.py | 14 +-
.../test_application_collections.py | 18 +-
.../hosted_apps}/test_application_desktop.py | 69 +-
.../hosted_apps}/test_application_export.py | 2 +-
.../hosted_apps}/test_application_hosting.py | 42 +-
.../test_application_personal_mcp.py | 32 +-
.../test_application_project_tools.py | 16 +-
.../hosted_apps}/test_application_projects.py | 9 +-
.../test_application_retirement.py | 10 +-
.../hosted_apps}/test_hosting_remediation.py | 28 +-
.../hosted_apps/test_schema_lifecycle.py | 60 +
.../tests/features/knowledge/__init__.py | 1 +
.../knowledge/kb_multimodal_assets_cases_1.py | 353 ++
.../knowledge/kb_multimodal_assets_cases_2.py | 120 +
.../knowledge/kb_multimodal_assets_support.py | 235 +
.../knowledge/kb_wiki_end_to_end_cases_1.py | 349 ++
.../knowledge/kb_wiki_end_to_end_cases_2.py | 175 +
.../knowledge/kb_wiki_end_to_end_support.py | 197 +
.../knowledge/test_host_contributions.py | 68 +
.../knowledge}/test_kb_backend_routing.py | 59 +-
.../knowledge}/test_kb_document_status.py | 7 +-
.../knowledge}/test_kb_index_queue.py | 5 +-
.../knowledge}/test_kb_index_worker.py | 12 +-
.../knowledge/test_kb_multimodal_assets.py | 37 +
.../knowledge}/test_kb_vector_safety.py | 51 +-
.../knowledge}/test_kb_vector_timeouts.py | 27 +-
.../knowledge/test_kb_wiki_end_to_end.py | 31 +
.../knowledge}/test_kb_wiki_pipeline.py | 24 +-
.../tests/features/marketplace/__init__.py | 1 +
.../marketplace/mcp_marketplace_cases_1.py | 432 ++
.../marketplace/mcp_marketplace_cases_2.py | 460 ++
.../marketplace/mcp_marketplace_cases_3.py | 457 ++
.../marketplace/mcp_marketplace_cases_4.py | 418 ++
.../marketplace/mcp_marketplace_support.py | 275 +
.../marketplace}/test_agent_marketplace.py | 49 +-
.../marketplace}/test_marketplace_service.py | 83 +-
.../test_marketplace_submission.py | 90 +-
.../marketplace/test_mcp_marketplace.py | 69 +
.../marketplace/test_oauth_sdk2_contract.py | 114 +
src/backend/tests/features/sites/__init__.py | 1 +
src/backend/tests/features/sites/conftest.py | 2 +
.../sites}/test_cloud_site_publish.py | 38 +-
.../sites}/test_design_pick.py | 2 +-
.../sites}/test_desktop_site_paths.py | 20 +-
.../sites}/test_local_project_site_publish.py | 18 +-
.../sites}/test_local_site_sources.py | 56 +-
.../sites}/test_site_access_password.py | 21 +-
.../sites}/test_site_contention_postgres.py | 17 +-
.../sites}/test_site_listing.py | 19 +-
.../sites}/test_site_plugin_upgrade.py | 40 +-
.../sites}/test_site_rate_limit.py | 2 +-
.../test_execution_identity_contract.py | 44 +
.../tests/features/test_host_runtime.py | 18 +
.../tests/features/test_installed_modules.py | 63 +
.../features/test_run_capability_host.py | 15 +
.../tests/features/workbench/__init__.py | 1 +
.../workbench}/test_plugin_ui_contract.py | 10 +-
.../workbench}/test_plugin_ui_routes.py | 8 +-
.../test_workbench_activity_routes.py | 13 +-
.../test_workbench_actual_process_observer.py | 4 +-
.../test_workbench_application_config_http.py | 12 +-
.../test_workbench_approval_lease_http.py | 16 +-
.../workbench}/test_workbench_auth_routes.py | 12 +-
.../test_workbench_background_subpage_http.py | 8 +-
.../test_workbench_candidate_storage.py | 12 +-
.../test_workbench_capability_routes.py | 11 +-
.../test_workbench_closing_isolation_http.py | 14 +-
.../test_workbench_cookie_boundaries.py | 2 +-
.../workbench}/test_workbench_coordination.py | 2 +-
.../test_workbench_diagnostic_failure_http.py | 6 +-
.../workbench}/test_workbench_diagnostics.py | 9 +-
.../workbench}/test_workbench_embed_routes.py | 2 +-
.../test_workbench_external_routes.py | 5 +-
.../workbench}/test_workbench_invalidation.py | 14 +-
.../workbench}/test_workbench_migration.py | 8 +-
.../test_workbench_migration_auth_gate.py | 6 +-
.../test_workbench_observer_paths.py | 2 +-
...st_workbench_official_capabilities_http.py | 4 +-
.../workbench}/test_workbench_origins.py | 10 +-
.../test_workbench_plugin_routes.py | 9 +-
.../test_workbench_preferences_routes.py | 9 +-
.../workbench}/test_workbench_protocol.py | 8 +-
.../test_workbench_provider_scope_http.py | 4 +-
...est_workbench_resource_owner_migrations.py | 2 +-
.../workbench}/test_workbench_resources.py | 58 +-
.../test_workbench_revocation_failure_http.py | 30 +-
.../test_workbench_routing_routes.py | 4 +-
.../test_workbench_state_binding.py | 2 +-
.../test_workbench_state_concurrency.py | 6 +-
.../workbench}/test_workbench_state_routes.py | 6 +-
.../test_workbench_upgrade_routes.py | 4 +-
.../workbench_capabilities_http_fixture.py | 6 +-
.../workbench_diagnostic_http_fixture.py | 2 +-
.../workbench_embed_http_fixture.py | 2 +-
.../workbench}/workbench_http_fixture.py | 2 +-
.../workbench_observed_runner_fixture.py | 4 +-
.../workbench}/workbench_observer_paths.py | 2 +-
.../workbench_overflow_http_fixture.py | 2 +-
.../workbench_plugin_http_fixture.py | 2 +-
.../workbench}/workbench_plugin_support.py | 0
.../workbench}/workbench_resource_observer.py | 6 +-
.../workbench_routing_http_fixture.py | 2 +-
src/backend/tests/infra/test_byte_range.py | 56 +
src/backend/tests/infra/test_core_cleanup.py | 23 +-
.../infra/test_db_off_loop_call_sites.py | 115 +
.../tests/infra/test_db_thread_budget.py | 120 +
.../infra/test_no_session_on_event_loop.py | 100 +
.../integration/background_command_server.py | 13 +-
.../integration/job_agent_local_smoke.py | 15 +-
.../tests/integration/job_local_smoke.py | 18 +-
.../tests/integration/job_managed_smoke.py | 12 +-
src/backend/tests/integrations/__init__.py | 1 +
.../tests/integrations/channels/__init__.py | 1 +
.../integrations/channels/bot_cases_1.py | 383 ++
.../integrations/channels/bot_cases_2.py | 362 ++
.../integrations/channels/bot_cases_3.py | 374 ++
.../integrations/channels/bot_cases_4.py | 383 ++
.../integrations/channels/bot_cases_5.py | 388 ++
.../integrations/channels/bot_cases_6.py | 145 +
.../integrations/channels/bot_support.py | 187 +
.../channels/test_adapter_method_binding.py | 16 +
.../channels/test_channel_bots.py | 115 +
.../channels}/test_channel_bots_api.py | 8 +-
.../channels}/test_channel_desktop_e2e.py | 54 +-
.../channels}/test_channel_desktop_http.py | 48 +-
.../test_channel_desktop_migration.py | 2 +-
.../channels}/test_channel_desktop_relay.py | 13 +-
.../test_channel_desktop_relay_api.py | 36 +-
.../channels}/test_channel_desktop_worker.py | 50 +-
.../test_channel_receiver_lifecycle.py | 10 +-
.../test_weixin_connection_health.py | 11 +-
.../channels}/test_weixin_qr_protocol.py | 7 +-
.../dingtalk}/test_dingtalk_integration.py | 51 +-
.../email}/test_email_integration.py | 70 +-
.../integrations/lark/test_app_lifecycle.py | 48 +
.../lark}/test_lark_auth_status.py | 39 +-
.../yida}/test_yida_integration.py | 29 +-
src/backend/tests/llm/command_test_support.py | 15 +-
.../tests/llm/e2e_multiturn_toolcall.py | 4 +-
...le_context_hook_user_id_gating_selftest.py | 8 +-
.../tests/llm/local_project_test_support.py | 29 +-
src/backend/tests/llm/mcp_env_debug.py | 6 +-
.../tests/llm/test_agent_api_execution.py | 15 +-
.../tests/llm/test_agent_api_runtime.py | 51 +-
.../tests/llm/test_api_protocol_backfill.py | 8 +-
src/backend/tests/llm/test_approval_modes.py | 24 +-
src/backend/tests/llm/test_artifact_refs.py | 2 +-
.../tests/llm/test_ask_user_question_tool.py | 14 +-
.../tests/llm/test_bash_process_tools.py | 23 +-
.../tests/llm/test_builtin_subagents.py | 19 +-
.../tests/llm/test_cancelled_replay.py | 2 +-
.../tests/llm/test_chat_history_tool.py | 18 +-
.../tests/llm/test_chat_model_stream_retry.py | 8 +-
src/backend/tests/llm/test_compaction.py | 2 +-
src/backend/tests/llm/test_compaction_e2e.py | 14 +-
.../tests/llm/test_compaction_integration.py | 22 +-
.../tests/llm/test_compaction_mid_turn.py | 35 +-
.../tests/llm/test_compaction_observation.py | 16 +-
.../tests/llm/test_compaction_progress.py | 56 +-
.../llm/test_compaction_summary_requests.py | 6 +-
.../tests/llm/test_compaction_watermark.py | 38 +-
.../llm/test_configured_reasoning_effort.py | 10 +-
src/backend/tests/llm/test_context_adapter.py | 26 +-
src/backend/tests/llm/test_context_ir.py | 8 +-
.../llm/test_context_order_user_input.py | 16 +-
src/backend/tests/llm/test_context_probe.py | 4 +-
src/backend/tests/llm/test_context_safety.py | 28 +-
.../tests/llm/test_context_step_units.py | 25 +-
.../llm/test_context_usage_adversarial.py | 10 +-
.../tests/llm/test_context_usage_snapshot.py | 13 +-
src/backend/tests/llm/test_delta_coalescer.py | 145 +
.../tests/llm/test_execution_manifest.py | 10 +-
.../llm/test_execution_surface_limits.py | 37 +-
.../llm/test_execution_surface_snapshot.py | 24 +-
.../tests/llm/test_failover_context_safety.py | 8 +-
...st_file_context_offloads_blocking_parse.py | 25 +-
.../tests/llm/test_file_context_preview.py | 18 +-
.../llm/test_live_approval_integration.py | 8 +-
.../tests/llm/test_live_approval_repair.py | 30 +-
.../tests/llm/test_local_bash_permission.py | 52 +-
.../llm/test_local_confinement_contract.py | 11 +-
.../tests/llm/test_local_file_permission.py | 46 +-
.../tests/llm/test_local_myspace_block.py | 2 +-
.../tests/llm/test_local_project_cwd.py | 59 +-
.../tests/llm/test_local_project_delivery.py | 73 +-
.../tests/llm/test_local_project_pin.py | 80 +-
.../tests/llm/test_local_session_reference.py | 32 +-
.../tests/llm/test_manifest_mcp_client.py | 27 +-
src/backend/tests/llm/test_mcp_factory_e2e.py | 88 +
.../tests/llm/test_mcp_human_wait_timeout.py | 271 +
.../tests/llm/test_mcp_native_adversarial.py | 219 +
.../tests/llm/test_mcp_native_capabilities.py | 169 +
.../tests/llm/test_mcp_parallel_execution.py | 36 +-
src/backend/tests/llm/test_mcp_pool_cache.py | 203 -
.../tests/llm/test_mcp_runtime_pool.py | 272 +
.../tests/llm/test_mcp_session_ownership.py | 128 +
src/backend/tests/llm/test_mcp_timeouts.py | 32 +-
.../llm/test_migration_vendor_datasource.py | 114 +
src/backend/tests/llm/test_model_failover.py | 27 +-
src/backend/tests/llm/test_model_steps.py | 26 +-
.../tests/llm/test_model_usage_extraction.py | 2 +-
.../llm/test_model_usage_scope_teardown.py | 2 +-
src/backend/tests/llm/test_offloader.py | 40 +-
src/backend/tests/llm/test_openai_provider.py | 14 +-
.../llm/test_permission_ticket_isolation.py | 21 +-
.../llm/test_plan_update_tool_description.py | 10 +-
.../llm/test_post_turn_measured_compaction.py | 14 +-
.../llm/test_project_environment_factory.py | 32 +-
.../llm/test_project_environment_policy.py | 14 +-
src/backend/tests/llm/test_protocol_probe.py | 8 +-
.../tests/llm/test_read_image_native.py | 26 +-
.../tests/llm/test_reasoning_budget.py | 6 +-
.../llm/test_reasoning_echo_formatter.py | 4 +-
src/backend/tests/llm/test_reasoning_probe.py | 2 +-
.../tests/llm/test_reasoning_replay_policy.py | 10 +-
.../llm/test_reasoning_retained_in_history.py | 8 +-
.../llm/test_responses_reasoning_replay.py | 7 +-
.../llm/test_responses_thinking_stream.py | 94 +-
.../tests/llm/test_responses_tool_call_id.py | 20 +-
.../llm/test_sandbox_session_resolution.py | 8 +-
.../llm/test_selected_model_context_window.py | 27 +-
.../llm/test_shared_human_interactions.py | 8 +-
.../tests/llm/test_single_turn_protocol.py | 2 +-
.../tests/llm/test_stream_wrapper_cleanup.py | 30 +
.../llm/test_structured_reasoning_flag.py | 4 +-
.../tests/llm/test_subagent_cancellation.py | 25 +-
.../tests/llm/test_subagent_model_config.py | 17 +-
src/backend/tests/llm/test_subagent_resume.py | 10 +-
.../tests/llm/test_subagent_tool_streaming.py | 17 +-
src/backend/tests/llm/test_system_reminder.py | 7 +-
.../test_thinking_modes_across_protocols.py | 10 +-
.../tests/llm/test_tool_call_identity.py | 2 +-
.../tests/llm/test_tool_media_no_disk.py | 10 +-
.../llm/test_tool_permission_hardening.py | 17 +-
.../tests/llm/test_tool_permissions.py | 43 +-
.../tests/llm/test_tool_result_images.py | 46 +-
src/backend/tests/llm/test_vendor_timeouts.py | 4 +-
src/backend/tests/llm/test_vision_bridge.py | 20 +-
.../tests/llm/test_vision_bridge_e2e.py | 6 +-
.../tests/llm/test_vision_injection.py | 76 +-
.../tests/llm/test_vision_progress_events.py | 44 +-
.../tests/llm/test_vision_role_capability.py | 7 +-
src/backend/tests/llm/test_workspace.py | 13 +-
.../tests/llm/test_workspace_profiles.py | 99 +-
src/backend/tests/llm/tool_call_debug.py | 4 +
.../tests/llm/vision_fullstack_check.py | 4 +-
src/backend/tests/llm/vision_live_check.py | 12 +-
.../tests/llm/vision_role_live_check.py | 10 +-
src/backend/tests/mcp/test_internet_search.py | 54 +-
.../tests/mcp/test_wiki_tool_gating.py | 13 +-
.../tests/mcp_servers/test_search_batch.py | 138 +-
.../tests/mcp_servers/test_web_fetch.py | 3 +-
.../tests/memory/outbox_test_support.py | 3 +-
.../test_ce_overlay_memory_contracts.py | 41 +-
src/backend/tests/memory/test_graph_memory.py | 22 +-
.../memory/test_identity_frozen_block.py | 6 +-
.../tests/memory/test_mem0_real_lifecycle.py | 1 +
.../tests/memory/test_memory_outbox.py | 4 +-
.../memory/test_memory_outbox_receipts.py | 8 +-
.../memory/test_memory_outbox_recovery.py | 16 +-
.../memory/test_memory_retrieval_lifecycle.py | 36 +-
.../memory/test_session_frozen_snapshot.py | 34 +-
.../misc/chat_metadata_merge_selftest.py | 2 +-
src/backend/tests/misc/conftest.py | 15 +-
src/backend/tests/misc/test_identity_dedup.py | 2 +-
.../tests/ontology/test_ontology_reviewer.py | 40 +-
.../ontology/test_ontology_revision_accept.py | 10 +-
.../test_ontology_workflow_evidence.py | 17 +-
.../agent_mention_parse_selftest.py | 2 +-
.../orchestration/automation_test_support.py | 17 +
.../orchestration/command_test_support.py | 2 +-
.../tests/orchestration/hook_support.py | 53 +
.../orchestration/recovery_test_support.py | 17 +-
.../orchestration/router_strategy_selftest.py | 2 +-
.../orchestration/routing_import_selftest.py | 2 +-
.../orchestration/stream_protocol_selftest.py | 7 +-
.../streaming_tool_call_dedupe_selftest.py | 15 +-
.../orchestration/test_agent_api_recovery.py | 40 +-
.../test_agent_runtime_resources.py | 20 +
.../test_agentscope_hook_adapter.py | 31 +-
.../test_automation_admission.py | 50 +-
.../orchestration/test_automation_handoff.py | 50 +-
.../test_automation_live_conversations.py | 125 +-
.../orchestration/test_automation_reaper.py | 4 +-
.../test_automation_unbounded.py | 65 +-
.../test_autonomous_loop_driver.py | 12 +-
.../orchestration/test_background_commands.py | 87 +-
.../test_chat_resume_checkpoint.py | 18 +-
.../test_chat_run_cancel_persistence.py | 57 +-
.../test_chat_run_follow_recovery.py | 104 +-
.../orchestration/test_chat_run_human_wait.py | 24 +-
.../orchestration/test_chat_run_recovery.py | 37 +-
.../test_chat_run_restart_recovery.py | 89 +-
.../test_chat_run_worker_recovery.py | 56 +-
.../tests/orchestration/test_chat_steer.py | 104 +-
.../orchestration/test_command_events.py | 61 +-
.../orchestration/test_command_recovery.py | 158 +-
.../test_command_worker_lifecycle.py | 18 +-
.../orchestration/test_context_budget_stop.py | 41 +-
.../orchestration/test_context_provenance.py | 11 +-
.../test_context_stats_finalization_race.py | 6 +-
.../test_direct_subagent_routing.py | 79 +-
.../test_event_offset_fencing.py | 11 +-
.../test_execution_manifest_binding.py | 128 +-
.../tests/orchestration/test_finish_guard.py | 79 +-
.../test_harness_e2e_fault_matrix.py | 18 +-
.../test_harness_ledger_postgres.py | 4 +-
.../test_harness_process_restart.py | 52 -
.../test_harness_usage_ledger.py | 57 +-
.../tests/orchestration/test_hook_bus.py | 141 +
.../orchestration/test_inactivity_watchdog.py | 2 +-
.../orchestration/test_inflight_message.py | 40 +-
.../test_job_callback_transport.py | 7 +-
.../orchestration/test_job_conflict_policy.py | 13 +-
.../orchestration/test_job_derived_agent.py | 56 +-
.../tests/orchestration/test_job_failfast.py | 35 +-
.../test_job_inherited_grants.py | 18 +-
.../orchestration/test_job_local_callback.py | 5 +-
.../orchestration/test_job_managed_runtime.py | 50 +-
.../orchestration/test_job_orphan_reaper.py | 25 +-
.../orchestration/test_job_resilience.py | 4 +-
.../orchestration/test_job_sdk_ledger.py | 2 +-
.../orchestration/test_job_ui_progress.py | 12 +-
.../test_job_wake_delivery_format.py | 2 +-
.../test_job_wake_message_visibility.py | 34 +-
.../tests/orchestration/test_job_wake_mode.py | 36 +-
.../test_job_windows_attempt_paths.py | 5 +-
.../test_loop_reviewer_registry_tool.py | 4 +-
.../test_missing_tool_call_synthesis.py | 4 +-
.../test_model_progress_signal.py | 60 +-
.../test_model_steps_pipeline.py | 25 +-
.../test_model_steps_plan_projection.py | 66 +-
.../orchestration/test_model_steps_worker.py | 47 +-
.../orchestration/test_observation_writer.py | 146 +
.../orchestration/test_pending_workflows.py | 68 +
.../orchestration/test_plan_early_cancel.py | 84 +
.../test_plan_progress_closeout.py | 9 +-
.../orchestration/test_plan_stale_reminder.py | 57 +-
.../test_project_api_recovery.py | 25 +-
.../test_project_init_stream_input.py | 5 +-
.../orchestration/test_run_event_stream.py | 16 +-
.../tests/orchestration/test_run_journal.py | 9 +-
.../test_run_journal_postgres.py | 15 +-
.../test_runtime_phase_lifecycle.py | 203 +
.../orchestration/test_site_mode_prompt.py | 11 +-
.../tests/orchestration/test_stale_reaper.py | 123 +-
.../tests/orchestration/test_steer_queue.py | 13 +-
.../test_structured_reasoning_protocol.py | 2 +-
.../test_thinking_block_persistence.py | 22 +-
.../test_thinking_suppression.py | 2 +-
.../orchestration/test_tool_effect_ledger.py | 1405 +----
.../test_tool_effect_ledger_2.py | 429 ++
.../test_tool_effect_ledger_3.py | 475 ++
.../test_tool_effect_ledger_4.py | 459 ++
.../test_tool_effect_ledger_5.py | 133 +
.../test_user_question_passthrough.py | 42 +-
.../tests/orchestration/test_worker_base.py | 3 +-
.../test_workflow_hint_consistency.py | 2 +-
.../test_workflow_model_runtime.py | 10 +-
.../orchestration/tool_effect_support.py | 81 +
.../{ => personal_agent}/test_device_link.py | 8 +-
.../prompts/test_desktop_delivery_tools.py | 2 +-
.../tests/prompts/test_desktop_environment.py | 13 +-
.../prompts/test_desktop_prompt_versions.py | 57 +-
...est_local_mode_drops_container_sections.py | 16 +-
.../prompts/test_local_project_section.py | 16 +-
.../tests/prompts/test_project_isolation.py | 2 +-
.../prompts/test_prompt_cache_full_hash.py | 4 +-
.../test_prompt_parts_from_filesystem.py | 56 +-
.../prompts/test_prompt_version_freshness.py | 44 +-
.../tests/run_browser_opensandbox_e2e.py | 8 +-
.../tests/run_command_opensandbox_e2e.py | 12 +-
.../tests/run_personal_agent_cli_grant_e2e.py | 3 +-
src/backend/tests/run_personal_agent_e2e.py | 6 +-
.../tests/run_personal_agent_key_e2e.py | 2 +-
...run_personal_agent_project_sessions_e2e.py | 5 +-
.../tests/sandbox/e2e_myspace_bulk_live.py | 6 +-
.../tests/sandbox/fixtures/egress_gateway.py | 14 +-
.../tests/sandbox/provider_test_support.py | 5 +-
.../test_adopt_respects_session_binding.py | 3 -
.../tests/sandbox/test_artifact_streaming.py | 8 +-
.../sandbox/test_cloud_process_sessions.py | 12 +-
.../tests/sandbox/test_controller_capacity.py | 37 +
.../sandbox/test_creation_adoption_race.py | 9 +-
.../test_credential_purge_preserves_mounts.py | 37 +-
.../sandbox/test_cube_skill_push_ownership.py | 7 +-
.../sandbox/test_idle_reclaim_snapshot.py | 3 +-
.../sandbox/test_multiworker_lifecycle.py | 2 +-
.../sandbox/test_multiworker_transactions.py | 2 +-
.../test_opensandbox_controller_patch.py | 2 +-
.../sandbox/test_opensandbox_expirations.py | 4 +-
.../sandbox/test_opensandbox_provider.py | 30 +
src/backend/tests/sandbox/test_os_sandbox.py | 135 +-
.../sandbox/test_pool_retirement_guards.py | 18 +-
.../sandbox/test_product_session_scope.py | 2 +-
.../tests/sandbox/test_runner_package.py | 2 +-
.../tests/sandbox/test_runner_user_env.py | 119 +
.../tests/sandbox/test_runtime_ownership.py | 61 +
.../tests/sandbox/test_sandbox_accounting.py | 14 +-
.../tests/sandbox/test_sandbox_init.py | 70 +
.../tests/sandbox/test_sandbox_packaging.py | 18 +-
.../sandbox/test_service_loop_affinity.py | 4 +-
.../tests/sandbox/test_session_affinity.py | 2 +-
.../sandbox/test_session_lock_cross_loop.py | 2 +-
.../tests/sandbox/test_user_bound_sandbox.py | 21 -
.../services/project_instructions_support.py | 5 +-
.../tests/services/test_artifact_reader.py | 41 +-
.../tests/services/test_artifact_summary.py | 20 +-
src/backend/tests/services/test_audio_live.py | 2 +-
.../tests/services/test_audio_models.py | 16 +-
.../tests/services/test_audio_routes.py | 6 +-
.../services/test_audio_transcription.py | 4 +-
.../tests/services/test_chat_mode_service.py | 3 +-
.../services/test_chat_reference_service.py | 11 +-
.../tests/services/test_chat_sequencer.py | 74 +-
.../tests/services/test_chat_titles.py | 35 +-
.../services/test_kb_multimodal_assets.py | 657 ---
.../tests/services/test_kb_wiki_end_to_end.py | 663 ---
.../test_local_instruction_hierarchy.py | 200 +
.../services/test_memory_settings_service.py | 16 +-
.../services/test_model_config_consistency.py | 50 +-
.../services/test_model_config_export.py | 8 +-
.../services/test_plan_execution_snapshot.py | 2 +-
.../test_project_context_no_inventory.py | 8 +-
.../services/test_project_environment.py | 17 +-
.../services/test_project_instructions.py | 39 +-
.../test_project_instructions_dispatch.py | 26 +-
.../tests/services/test_retire_chart_mcp.py | 2 +-
.../services/test_turbo_capability_config.py | 14 +-
.../services/test_user_model_selection.py | 13 +-
.../services/user_folder_service_selftest.py | 6 +-
.../tests/skills/agent_skills_selftest.py | 4 +-
src/backend/tests/skills/backends_selftest.py | 6 +-
src/backend/tests/skills/loader_selftest.py | 7 +-
.../tests/skills/skill_loading_selftest.py | 2 +-
.../tests/skills/skill_selection_selftest.py | 8 +-
.../skills/test_database_backend_lazy.py | 10 +-
.../tests/skills/test_deps_detector.py | 2 +-
.../skills/test_read_artifact_pptx_xlsx.py | 54 +-
src/backend/tests/skills/test_registry.py | 6 +-
.../tests/skills/test_sandbox_skill_purge.py | 17 +-
.../skills/test_skill_deps_aggregator.py | 20 +-
.../tests/skills/test_skill_publication.py | 67 +-
.../skills/test_skill_publication_docker.py | 2 +-
.../skills/test_skill_selector_context_ir.py | 6 +-
.../tests/skills/test_skill_zip_export.py | 20 +-
.../tests/test_add_agent_permission.py | 4 +-
.../tests/test_agent_manager_plugin.py | 13 +-
src/backend/tests/test_agent_markdown.py | 7 +-
.../tests/test_agent_version_history.py | 15 +
.../tests/test_automation_manual_trigger.py | 77 -
.../tests/test_automation_oneshot_finalize.py | 109 -
src/backend/tests/test_browser_prewarm.py | 75 +-
src/backend/tests/test_browser_remediation.py | 6 +-
.../tests/test_browser_resource_assets.py | 6 +-
.../tests/test_browser_resource_security.py | 39 +-
.../tests/test_browser_resource_transport.py | 14 +-
src/backend/tests/test_ce_agent_visibility.py | 6 +-
.../tests/test_ce_runtime_contracts.py | 47 +-
src/backend/tests/test_channel_bots.py | 2109 --------
src/backend/tests/test_citation_anchor.py | 16 +-
src/backend/tests/test_cli_cold_bootstrap.py | 48 +
src/backend/tests/test_cli_engine.py | 346 ++
.../tests/test_content_block_id_migration.py | 2 +-
src/backend/tests/test_credential_registry.py | 44 +
src/backend/tests/test_db_import_order.py | 103 +
.../tests/test_default_plugin_bootstrap.py | 10 +-
.../tests/test_desktop_observability.py | 633 ---
.../test_explicit_capability_invocation.py | 21 +-
src/backend/tests/test_extensions_layout.py | 28 +-
src/backend/tests/test_hook_contributions.py | 47 +
.../tests/test_iter_budget_middleware.py | 69 +-
src/backend/tests/test_leader_election.py | 48 +
.../tests/test_local_mcp_http_readiness.py | 33 +
.../tests/test_local_memory_defaults.py | 3 +-
src/backend/tests/test_local_onboarding.py | 95 +
.../tests/test_local_plugin_sidecars.py | 75 +-
src/backend/tests/test_local_profile.py | 158 +-
src/backend/tests/test_local_subprocess.py | 95 +-
src/backend/tests/test_management_failures.py | 8 +-
src/backend/tests/test_mcp_canonical_url.py | 30 +
src/backend/tests/test_mcp_marketplace.py | 1758 -------
src/backend/tests/test_package_boundaries.py | 16 +-
src/backend/tests/test_package_sources.py | 23 +
src/backend/tests/test_personal_agent.py | 16 +-
.../tests/test_personal_agent_access.py | 10 +-
.../tests/test_personal_agent_capabilities.py | 14 +-
.../test_personal_agent_channel_files.py | 8 +-
.../test_personal_agent_channel_sessions.py | 11 +-
.../tests/test_personal_agent_cold_start.py | 10 +-
.../test_personal_agent_computer_wait.py | 6 +-
.../tests/test_personal_agent_delivery.py | 11 +-
.../tests/test_personal_agent_dreaming.py | 4 +-
.../tests/test_personal_agent_fixes.py | 40 +-
.../tests/test_personal_agent_monitor.py | 3 +-
.../tests/test_personal_agent_persona.py | 15 +-
.../tests/test_personal_agent_prompt_cache.py | 22 +
.../tests/test_personal_agent_routing.py | 28 +-
.../tests/test_personal_agent_session_tool.py | 5 +-
.../tests/test_personal_agent_sessions.py | 12 +-
.../tests/test_personal_agent_steer.py | 21 +-
.../test_personal_agent_tool_regressions.py | 30 +-
.../tests/test_personal_canonical_history.py | 4 +-
.../test_personal_task_transcript_order.py | 18 +-
src/backend/tests/test_plugin_components.py | 73 +-
.../tests/test_plugin_manager_plugin.py | 17 +-
.../tests/test_plugin_progressive_loading.py | 89 +-
.../tests/test_plugin_resource_contract.py | 2 +-
src/backend/tests/test_plugin_system.py | 17 +-
src/backend/tests/test_redis_stream_pool.py | 6 +-
.../test_retired_batch_chat_admission_http.py | 32 +-
src/backend/tests/test_route_import_order.py | 74 +
.../test_script_runner_process_limits.py | 10 +-
.../tests/test_session_capabilities.py | 4 +-
.../tests/test_session_database_isolation.py | 23 +
src/backend/tests/test_sidebar_task_status.py | 13 +-
.../tests/test_skill_manager_plugin.py | 23 +-
src/backend/tests/test_tool_contributions.py | 90 +
.../tests/test_user_agent_resources.py | 16 +-
src/backend/tests/test_user_cleanup.py | 34 +
src/backend/tests/test_visualize_plugin.py | 18 +-
src/backend/tests/test_yida_local_login.py | 12 +-
src/frontend/Dockerfile | 3 +-
src/frontend/Dockerfile.dockerignore | 8 +-
src/frontend/package-lock.json | 263 +
src/frontend/package.json | 4 +
src/frontend/scripts/archive-e2e-fixtures.py | 4 +-
.../scripts/chat-customization-environment.py | 2 +-
.../scripts/component-e2e-environment.py | 2 +-
.../scripts/customer-devkit-environment.py | 4 +-
.../scripts/desktop-lint-baseline.json | 1 -
.../scripts/grapheme-e2e-environment.py | 2 +-
src/frontend/scripts/test-attachment-send.tsx | 4 +-
.../scripts/test-canvas-quote-browser.mjs | 131 +
.../scripts/test-canvas-viewers-browser.mjs | 213 +
.../scripts/test-canvas-zoom-browser.mjs | 254 +
src/frontend/scripts/test-chat-fork.tsx | 2 +-
.../scripts/test-desktop-login-browser.mjs | 2 +-
src/frontend/scripts/test-mcp-tool-output.ts | 75 +
.../scripts/test-queued-invocation.ts | 12 +
.../scripts/test-reference-motion-browser.mjs | 34 +-
.../test-sidebar-task-status-browser.mjs | 16 +-
.../scripts/test-subagent-canvas-browser.mjs | 3 +-
.../scripts/test-turn-status-alignment.mjs | 2 +-
.../test-workbench-capabilities-browser.mjs | 2 +-
.../test-workbench-diagnostics-browser.mjs | 2 +-
.../scripts/test-workbench-embed-browser.mjs | 2 +-
.../scripts/test-workbench-plugin-browser.mjs | 4 +-
.../scripts/test-workspace-http-browser.mjs | 4 +-
.../automation/AutomationTaskSections.tsx | 6 +-
.../automation/RunTimelinePanel.tsx | 64 +-
.../components/automation/automationUtils.ts | 9 +-
.../components/canvas/CanvasFileHeader.css | 6 +
.../components/canvas/CanvasFileHeader.tsx | 2 +
.../components/canvas/CanvasFileRenderers.tsx | 338 --
.../src/components/canvas/CanvasPanel.tsx | 45 +-
.../components/canvas/CanvasZoomControl.tsx | 81 +
.../src/components/canvas/FileTextPanel.tsx | 6 +-
.../components/canvas/UniverSpreadsheet.tsx | 31 +
.../src/components/canvas/canvasDocument.ts | 121 +
.../components/canvas/canvasFileCategory.ts | 22 -
.../src/components/canvas/canvasZoom.ts | 85 +
.../src/components/canvas/find/CanvasFind.css | 45 +
.../src/components/canvas/find/CanvasFind.tsx | 196 +
.../components/canvas/find/domFindSource.ts | 17 +
.../src/components/canvas/find/index.ts | 1 +
.../src/components/canvas/find/textSearch.ts | 76 +
.../canvas/outline/CanvasOutline.css | 143 +
.../canvas/outline/CanvasOutline.tsx | 225 +
.../components/canvas/outline/domOutline.ts | 54 +
.../src/components/canvas/outline/index.ts | 1 +
.../canvas/renderers/DocxRenderer.tsx | 100 +
.../canvas/renderers/FallbackRenderers.tsx | 40 +
.../canvas/renderers/HtmlRenderer.tsx | 62 +
.../canvas/renderers/ImageRenderer.tsx | 102 +
.../canvas/renderers/PdfRenderer.tsx | 60 +
.../components/canvas/renderers/PdfViewer.css | 104 +
.../components/canvas/renderers/PdfViewer.tsx | 399 ++
.../canvas/renderers/TextRenderers.css | 66 +
.../canvas/renderers/TextRenderers.tsx | 138 +
.../src/components/canvas/renderers/index.ts | 6 +
.../src/components/canvas/renderers/pdfjs.ts | 68 +
.../canvas/renderers/previewError.ts | 11 +
.../selection/SpreadsheetQuoteButton.css | 56 +
.../selection/SpreadsheetQuoteButton.tsx | 23 +
.../components/canvas/selection/canvasChat.ts | 48 +
.../canvas/selection/canvasLocator.ts | 37 +
.../canvas/selection/useCanvasSelection.tsx | 33 +
.../src/components/chat/AgentCursor.tsx | 72 +
.../src/components/chat/InputArea.tsx | 22 +-
.../src/components/chat/MessageBody.tsx | 111 +-
.../src/components/chat/MessageBubble.tsx | 5 +-
.../components/chat/OntologyRevisionPanel.tsx | 7 -
.../components/chat/StreamWaitIndicator.tsx | 4 +-
.../components/chat/TurnStatusIndicator.tsx | 56 +-
.../components/chat/useMessageSelection.tsx | 298 +-
.../src/components/common/AgentMark.tsx | 48 +
.../src/components/common/ElapsedTimer.tsx | 8 +-
.../src/components/common/RollingText.tsx | 105 +
.../common/selection/SelectionToolbar.css | 79 +
.../common/selection/useSelectionToolbar.tsx | 226 +
.../src/components/file/FilePreviewPane.tsx | 80 +-
.../components/file/FilePreviewViewers.tsx | 30 +-
.../src/components/file/MediaPlayer.css | 84 +
.../src/components/file/MediaPlayer.tsx | 66 +
.../src/components/file/filePreviewUrls.ts | 5 -
.../shared/PersonalAgentNavStatus.tsx | 9 +-
.../personal-agent/shared/notifications.ts | 15 +-
.../projects/CreateProjectModal.tsx | 10 +-
.../src/components/sidebar/ChatSidebar.tsx | 101 +-
.../src/components/tool/CompactionStepRow.tsx | 10 +-
.../src/components/tool/PendingStepRow.tsx | 34 -
.../components/tool/PluginToolOutputBody.tsx | 4 +-
.../src/components/tool/ThinkingStepRow.tsx | 7 +-
.../src/components/tool/ToolCallRow.tsx | 72 +-
.../components/tool/ToolCapabilityIcon.tsx | 8 +-
.../src/components/tool/ToolKindStack.tsx | 69 +
.../components/tool/ToolOutputRenderer.tsx | 2 +
.../src/components/tool/ToolRunShell.tsx | 151 +-
.../src/components/tool/renderers/utils.tsx | 7 +-
.../src/components/tool/toolIcons.tsx | 68 +
src/frontend/src/hooks/chatHistoryMessage.ts | 10 +-
src/frontend/src/hooks/chatStream/consume.ts | 1 -
.../src/hooks/chatStream/lifecycleAndTools.ts | 46 +-
.../src/hooks/chatStream/messageStore.ts | 6 +-
src/frontend/src/hooks/chatStream/snapshot.ts | 1 -
src/frontend/src/hooks/chatStream/state.ts | 1 -
.../hooks/chatStream/toolResultsAndMeta.ts | 1 -
src/frontend/src/hooks/streamQueue.ts | 6 +-
src/frontend/src/hooks/streamSend.ts | 17 +-
src/frontend/src/hooks/useActivityAge.ts | 47 +
src/frontend/src/hooks/useStallDetector.ts | 6 +-
src/frontend/src/hooks/useToolCapability.ts | 18 +-
src/frontend/src/i18n/en/canvas.ts | 39 +
src/frontend/src/i18n/en/chat.ts | 5 +-
src/frontend/src/i18n/en/lab.ts | 4 +-
src/frontend/src/i18n/en/myspace.ts | 2 +
src/frontend/src/i18n/en/panels.ts | 14 +-
src/frontend/src/i18n/en/settings.ts | 4 +
src/frontend/src/i18n/en/tool.ts | 27 +-
src/frontend/src/plugin-ui/README.md | 4 +-
.../src/stores/automationChatStore.ts | 35 +-
src/frontend/src/stores/canvasStore.ts | 39 +-
src/frontend/src/stores/composerStore.ts | 4 +-
src/frontend/src/styles/canvas.css | 290 +-
src/frontend/src/styles/chat.css | 1 +
.../src/styles/chat/agent-activity.css | 226 +
src/frontend/src/styles/chat/attachments.css | 18 +-
.../src/styles/chat/import-browser.css | 54 -
.../src/styles/chat/message-actions.css | 68 -
src/frontend/src/styles/chat/messages.css | 12 +-
src/frontend/src/styles/chat/turn-status.css | 28 +-
src/frontend/src/styles/sidebar-history.css | 25 +
src/frontend/src/styles/tool.css | 144 +-
src/frontend/src/styles/variables.css | 15 +
src/frontend/src/types/automations.ts | 2 +-
src/frontend/src/types/conversations.ts | 23 +-
src/frontend/src/types/messages.ts | 1 -
src/frontend/src/utils/chatInvocation.ts | 10 +
src/frontend/src/utils/citations.ts | 5 +-
src/frontend/src/utils/elapsed.ts | 5 +
src/frontend/src/utils/fileParser.ts | 2 +
src/frontend/src/utils/filePreviewKind.ts | 92 +
src/frontend/src/utils/filePreviewSafety.ts | 24 +-
src/frontend/src/utils/index.ts | 2 +-
.../src/utils/internetSearchConfig.ts | 8 +-
src/frontend/src/utils/mcpResult.ts | 33 +
src/frontend/src/utils/quotedFollowUp.ts | 64 +
src/frontend/src/utils/toolActionLabel.ts | 84 +-
src/frontend/src/utils/toolIconKind.ts | 12 +
src/frontend/src/utils/turnActivity.ts | 69 +
2909 files changed, 112106 insertions(+), 91117 deletions(-)
create mode 100644 .claude/skills/hugagent-backend-dev/templates/extension.py
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/attachment.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/automation.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/check.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/chevron-right.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/close.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/knowledge.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/my-space.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/new-chat.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/search.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/send.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/settings.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/skill.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/favicon.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-black.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-blue.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-white.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-black.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-white.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-reverse.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-small.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark.svg
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/appTheme.v2.ts
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.css
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.json
create mode 100644 .claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/motionTokens.v2.ts
delete mode 100644 docker/Dockerfile.opensandbox-server.dockerignore
delete mode 100644 docker/Dockerfile.personal-agent-test
rename docker-compose.application-hosting.yml => docker/application-hosting/compose.application-hosting.yml (90%)
rename docker/{ => object-storage}/Dockerfile.object-storage (100%)
rename docker/{ => object-storage}/compose.object-storage.yml (80%)
rename docker/{ => object-storage}/init-object-storage.py (100%)
rename docker/{ => opensandbox-server}/Dockerfile.opensandbox-server (52%)
create mode 100644 docker/opensandbox-server/Dockerfile.opensandbox-server.dockerignore
rename docker/{ => opensandbox-server}/opensandbox-config.toml.tpl (100%)
create mode 100644 docker/opensandbox-server/opensandbox_capacity.py
rename docker/{ => opensandbox-server}/opensandbox_expirations.py (100%)
rename docker/{ => opensandbox-server}/opensandbox_mount_recovery.py (91%)
rename docker/{ => opensandbox-server}/opensandbox_mount_worker.py (100%)
rename docker/{ => opensandbox-server}/opensandbox_object_mounts.py (100%)
rename docker/{ => opensandbox-server}/patch_opensandbox_server.py (96%)
rename docker/{ => opensandbox-server}/render-opensandbox-config.py (100%)
rename docker/{ => personal-agent}/Dockerfile.personal-agent (86%)
rename docker/{ => personal-agent}/compose.personal-agent.yml (91%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/helpers.rc (100%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/mimeapps.list (100%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/pa-browser (100%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/pa-browser-helper.desktop (100%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/pa-browser.desktop (100%)
rename docker/{personal-agent-desktop => personal-agent/desktop}/pa-stream.py (100%)
rename docker/{ => personal-agent}/hugagent-cli-entrypoint.sh (100%)
rename docker/{ => personal-agent}/personal-agent-entrypoint.sh (94%)
rename docker/{ => personal-agent}/render-personal-agent-config.py (100%)
rename docker/{ => sandbox}/Dockerfile.opensandbox (92%)
rename docker/{ => sandbox}/Dockerfile.script-runner (96%)
rename docker/{ => sandbox}/apt-skills.txt (100%)
rename docker/{ => sandbox}/package-skills.json (100%)
rename docker/{ => sandbox}/requirements-script-runner.txt (100%)
rename docker/{ => sandbox}/requirements-skills.txt (100%)
create mode 100644 docker/sandbox/sandbox-init.c
rename docker/{ => sandbox}/site-template/init-react-site.sh (100%)
rename docker/{ => sandbox}/site-template/react-vite/.npmrc (100%)
rename docker/{ => sandbox}/site-template/react-vite/index.html (100%)
rename docker/{ => sandbox}/site-template/react-vite/package-lock.json (100%)
rename docker/{ => sandbox}/site-template/react-vite/package.json (100%)
rename docker/{ => sandbox}/site-template/react-vite/src/App.jsx (100%)
rename docker/{ => sandbox}/site-template/react-vite/src/index.css (100%)
rename docker/{ => sandbox}/site-template/react-vite/src/lib/siteApi.js (100%)
rename docker/{ => sandbox}/site-template/react-vite/src/main.jsx (100%)
rename docker/{ => sandbox}/site-template/react-vite/src/pages/Home.jsx (100%)
rename docker/{ => sandbox}/site-template/react-vite/vite.config.mjs (100%)
rename docker/{ => sandbox}/verify-browser-runtime.py (100%)
delete mode 100644 document/en/deployment/artifact-storage-key-repair.md
delete mode 100644 document/en/deployment/offline-production.md
delete mode 100644 document/en/deployment/uos1070-desktop.md
delete mode 100644 document/en/development/container-layout.md
delete mode 100644 document/zh-CN/deployment/artifact-storage-key-repair.md
delete mode 100644 document/zh-CN/deployment/offline-production.md
delete mode 100644 document/zh-CN/deployment/uos1070-desktop.md
delete mode 100644 document/zh-CN/development/container-layout.md
delete mode 100644 examples/customer-chat/.env.example
delete mode 100644 examples/customer-chat/.gitignore
delete mode 100644 examples/customer-chat/README.md
delete mode 100644 examples/customer-chat/debug.html
delete mode 100644 examples/customer-chat/index.html
delete mode 100644 examples/customer-chat/package-lock.json
delete mode 100644 examples/customer-chat/package.json
delete mode 100644 examples/customer-chat/preview.html
delete mode 100644 examples/customer-chat/scripts/build-component.mjs
delete mode 100644 examples/customer-chat/scripts/developer-proxy.mjs
delete mode 100644 examples/customer-chat/scripts/test-package.mjs
delete mode 100644 examples/customer-chat/scripts/test-proxy.mjs
delete mode 100644 examples/customer-chat/session.html
delete mode 100644 examples/customer-chat/src/controls.ts
delete mode 100644 examples/customer-chat/src/css.d.ts
delete mode 100644 examples/customer-chat/src/customization.tsx
delete mode 100644 examples/customer-chat/src/debug.ts
delete mode 100644 examples/customer-chat/src/developer.css
delete mode 100644 examples/customer-chat/src/login.ts
delete mode 100644 examples/customer-chat/src/preview.tsx
delete mode 100644 examples/customer-chat/src/session.ts
delete mode 100644 examples/customer-chat/src/standalone.css
delete mode 100644 examples/customer-chat/src/standalone.ts
delete mode 100644 examples/customer-chat/src/theme.css
delete mode 100644 examples/customer-chat/vite.config.mjs
create mode 100644 packages/cli/.gitignore
create mode 100644 packages/cli/DESIGN.md
create mode 100644 packages/cli/README.md
create mode 100644 packages/cli/package-lock.json
create mode 100644 packages/cli/package.json
create mode 100644 packages/cli/src/config.ts
create mode 100644 packages/cli/src/engine/base.ts
create mode 100644 packages/cli/src/engine/engine.ts
create mode 100644 packages/cli/src/engine/errors.ts
create mode 100644 packages/cli/src/engine/headless.ts
create mode 100644 packages/cli/src/engine/http.ts
create mode 100644 packages/cli/src/engine/index.ts
create mode 100644 packages/cli/src/engine/local.ts
create mode 100644 packages/cli/src/engine/remote.ts
create mode 100644 packages/cli/src/engine/server-command.ts
create mode 100644 packages/cli/src/main.ts
create mode 100644 packages/cli/src/modes/interactive.ts
create mode 100644 packages/cli/src/modes/json.ts
create mode 100644 packages/cli/src/modes/print.ts
create mode 100644 packages/cli/src/modes/rpc.ts
create mode 100644 packages/cli/src/tui/app-editor.ts
create mode 100644 packages/cli/src/tui/app.ts
create mode 100644 packages/cli/src/tui/bash.ts
create mode 100644 packages/cli/src/tui/clipboard.ts
create mode 100644 packages/cli/src/tui/commands.ts
create mode 100644 packages/cli/src/tui/events.ts
create mode 100644 packages/cli/src/tui/footer.ts
create mode 100644 packages/cli/src/tui/format.ts
create mode 100644 packages/cli/src/tui/plan.ts
create mode 100644 packages/cli/src/tui/prompts/confirm.ts
create mode 100644 packages/cli/src/tui/prompts/question.ts
create mode 100644 packages/cli/src/tui/prompts/select.ts
create mode 100644 packages/cli/src/tui/theme.ts
create mode 100644 packages/cli/src/tui/transcript.ts
create mode 100644 packages/cli/src/version.ts
create mode 100644 packages/cli/test/cli-modes.test.mjs
create mode 100644 packages/cli/test/engine-fake-engine.mjs
create mode 100644 packages/cli/test/engine-fake-server.mjs
create mode 100644 packages/cli/test/engine-http.test.mjs
create mode 100644 packages/cli/test/engine-local.test.mjs
create mode 100644 packages/cli/test/tui-app.test.mjs
create mode 100644 packages/cli/test/tui-transcript.test.mjs
rename {examples/customer-chat => packages/cli}/tsconfig.json (50%)
create mode 100644 scripts/refactor/__init__.py
create mode 100644 scripts/refactor/check_ce_paths.py
create mode 100644 scripts/refactor/check_line_budget.py
create mode 100644 scripts/refactor/check_test_baseline.py
create mode 100644 scripts/refactor/import_contract.py
create mode 100644 scripts/refactor/rewrite_imports.py
create mode 100644 scripts/refactor/snapshot_routes.py
create mode 100644 scripts/refactor/snapshot_schema.py
create mode 100644 scripts/refactor/tests/test_edition_seams.py
create mode 100644 scripts/refactor/tests/test_guards.py
create mode 100644 scripts/refactor/tests/test_package_markers.py
create mode 100644 scripts/refactor/tests/test_private_imports.py
create mode 100644 scripts/refactor/tests/test_rewrite_imports.py
create mode 100644 scripts/refactor/tests/test_snapshot_schema.py
create mode 100644 scripts/refactor/tests/test_supplemental_moves.py
create mode 100644 scripts/refactor/tests/test_test_baseline.py
create mode 100644 scripts/refactor/tests/test_test_moves.py
create mode 100644 src/backend/alembic/versions/ce_0031_automation_lease_token.py
create mode 100644 src/backend/alembic/versions/ce_0032_automation_queue.py
create mode 100644 src/backend/alembic/versions/ce_0033_automation_targets.py
create mode 100644 src/backend/api/edition.py
rename src/backend/api/{routes/v1/agents.py => personal_agents.py} (94%)
create mode 100644 src/backend/api/routes/v1/chats/compaction.py
delete mode 100644 src/backend/api/routes/v1/desktop_capability.py
delete mode 100644 src/backend/api/routes/v1/kb.py
rename src/backend/{core/content => api/transports}/__init__.py (100%)
create mode 100644 src/backend/api/transports/run_sse.py
create mode 100644 src/backend/core/agent_runtime/README.md
create mode 100644 src/backend/core/agent_runtime/README.zh-CN.md
rename src/backend/core/{plugins/resources => agent_runtime}/__init__.py (100%)
create mode 100644 src/backend/core/agent_runtime/budget.py
create mode 100644 src/backend/core/agent_runtime/direct.py
create mode 100644 src/backend/core/agent_runtime/direct_state.py
create mode 100644 src/backend/core/agent_runtime/events.py
create mode 100644 src/backend/core/agent_runtime/host.py
rename src/backend/{tests/capabilities => core/agent_runtime/integrations}/__init__.py (100%)
rename src/backend/{orchestration => core/agent_runtime/integrations}/compaction_stream.py (95%)
create mode 100644 src/backend/core/agent_runtime/integrations/evolution.py
create mode 100644 src/backend/core/agent_runtime/integrations/memory.py
create mode 100644 src/backend/core/agent_runtime/integrations/memory_budget.py
create mode 100644 src/backend/core/agent_runtime/integrations/memory_identity.py
create mode 100644 src/backend/core/agent_runtime/integrations/memory_session.py
create mode 100644 src/backend/core/agent_runtime/integrations/memory_state.py
create mode 100644 src/backend/core/agent_runtime/integrations/ontology_evidence.py
create mode 100644 src/backend/core/agent_runtime/integrations/ontology_repair.py
create mode 100644 src/backend/core/agent_runtime/main_state.py
rename src/backend/{orchestration/subagents => core/agent_runtime}/plugin_visibility.py (91%)
create mode 100644 src/backend/core/agent_runtime/prepare_direct.py
create mode 100644 src/backend/core/agent_runtime/prepare_main.py
create mode 100644 src/backend/core/agent_runtime/runner.py
create mode 100644 src/backend/core/agent_runtime/skills.py
rename src/backend/{orchestration => core/agent_runtime}/strategy.py (100%)
create mode 100644 src/backend/core/agent_runtime/stream_direct.py
create mode 100644 src/backend/core/agent_runtime/stream_main.py
create mode 100644 src/backend/core/agent_runtime/targets.py
delete mode 100644 src/backend/core/agent_skills/backends/__init__.py
delete mode 100644 src/backend/core/agent_skills/cache_refresh.py
rename src/backend/core/{services/agent_api_service.py => auth/agent_api.py} (99%)
rename src/backend/core/{services => auth}/api_key_runs.py (85%)
rename src/backend/core/{services/api_key_service.py => auth/api_keys.py} (95%)
create mode 100644 src/backend/core/auth/bearer.py
create mode 100644 src/backend/core/auth/edition.py
create mode 100644 src/backend/core/auth/local_user_bootstrap.py
create mode 100644 src/backend/core/auth/local_user_policy.py
rename src/backend/core/{services/local_user_service.py => auth/local_users.py} (79%)
create mode 100644 src/backend/core/auth/local_users_constants.py
create mode 100644 src/backend/core/auth/model_selection.py
rename src/backend/core/auth/{account_policy.py => personal_account_policy.py} (100%)
rename src/backend/core/auth/{account_view.py => personal_account_view.py} (100%)
rename src/backend/core/auth/{edition_capabilities.py => personal_edition_capabilities.py} (100%)
rename src/backend/core/auth/{permissions_iface.py => personal_permissions_iface.py} (99%)
create mode 100644 src/backend/core/auth/public_identity.py
rename src/backend/core/{services/user_service.py => auth/users.py} (92%)
create mode 100644 src/backend/core/capabilities/agents/__init__.py
create mode 100644 src/backend/core/capabilities/agents/available_resources.py
create mode 100644 src/backend/core/capabilities/agents/base.py
create mode 100644 src/backend/core/capabilities/agents/base_constants.py
rename src/backend/core/{llm/builtin_subagents.py => capabilities/agents/builtin.py} (98%)
rename src/backend/core/{services/user_agent_service.py => capabilities/agents/edition.py} (64%)
rename src/backend/core/{services/agent_markdown.py => capabilities/agents/markdown.py} (100%)
create mode 100644 src/backend/core/capabilities/agents/version_history.py
create mode 100644 src/backend/core/capabilities/catalog/__init__.py
rename src/backend/core/{config/catalog.py => capabilities/catalog/api.py} (92%)
rename src/backend/core/{config/catalog_common.py => capabilities/catalog/common.py} (90%)
rename src/backend/core/{config/catalog_details.py => capabilities/catalog/details.py} (96%)
rename src/backend/core/{config/catalog_loader.py => capabilities/catalog/loader.py} (85%)
rename src/backend/core/{config/catalog_migration.py => capabilities/catalog/migration.py} (97%)
rename src/backend/core/{services/catalog_service.py => capabilities/catalog/overrides.py} (100%)
rename src/backend/core/{config/catalog_resolver.py => capabilities/catalog/resolver.py} (87%)
rename src/backend/core/{config/catalog_runtime.py => capabilities/catalog/runtime.py} (87%)
create mode 100644 src/backend/core/capabilities/identity_rules.py
rename src/backend/core/{services/desktop_capability_protocol.py => capabilities/manifest_protocol.py} (99%)
create mode 100644 src/backend/core/capabilities/mcp/__init__.py
rename src/backend/core/{services/mcp_builtin_specs.py => capabilities/mcp/builtin_specs.py} (94%)
rename src/backend/core/{services/edition_mcp.py => capabilities/mcp/edition.py} (100%)
rename src/backend/core/{services/mcp_management_service.py => capabilities/mcp/management.py} (89%)
create mode 100644 src/backend/core/capabilities/mcp/oauth.py
create mode 100644 src/backend/core/capabilities/mcp/oauth_storage.py
rename src/backend/core/{services/mcp_service.py => capabilities/mcp/service.py} (91%)
create mode 100644 src/backend/core/capabilities/mcp/tool_metadata.py
create mode 100644 src/backend/core/capabilities/mcp/tool_schema.py
create mode 100644 src/backend/core/capabilities/mcp/tool_schema_backfill.py
create mode 100644 src/backend/core/capabilities/plugins/README.md
rename src/backend/core/{ => capabilities}/plugins/__init__.py (100%)
rename src/backend/core/{ => capabilities}/plugins/connections.py (83%)
create mode 100644 src/backend/core/capabilities/plugins/management/__init__.py
rename src/backend/core/{ => capabilities}/plugins/management/admin_config.py (88%)
rename src/backend/core/{services/manager_bundle_upgrade.py => capabilities/plugins/management/bundle_upgrade.py} (92%)
rename src/backend/core/{services/cloud_management.py => capabilities/plugins/management/cloud.py} (78%)
rename src/backend/core/{ => capabilities}/plugins/management/components.py (88%)
rename src/backend/core/{services/management_contract.py => capabilities/plugins/management/contract.py} (100%)
rename src/backend/core/{ => capabilities}/plugins/management/defaults.py (93%)
rename src/backend/core/{ => capabilities}/plugins/management/details.py (58%)
rename src/backend/core/{services/management_errors.py => capabilities/plugins/management/errors.py} (100%)
rename src/backend/core/{ => capabilities}/plugins/management/installation.py (81%)
rename src/backend/core/{ => capabilities}/plugins/management/lifecycle.py (87%)
rename src/backend/core/{services/management_package.py => capabilities/plugins/management/package.py} (91%)
rename src/backend/core/{ => capabilities}/plugins/management/packages.py (70%)
rename src/backend/core/{ => capabilities}/plugins/management/projection.py (63%)
rename src/backend/core/{ => capabilities}/plugins/management/queries.py (86%)
rename src/backend/core/{ => capabilities}/plugins/packaging/__init__.py (100%)
rename src/backend/core/{ => capabilities}/plugins/packaging/archive.py (96%)
rename src/backend/core/{ => capabilities}/plugins/packaging/definitions.py (94%)
rename src/backend/core/{ => capabilities}/plugins/packaging/importer.py (96%)
rename src/backend/core/{ => capabilities}/plugins/packaging/models.py (100%)
create mode 100644 src/backend/core/capabilities/plugins/packaging/presentation.py
rename src/backend/core/{ => capabilities}/plugins/packaging/runtime_assets.py (100%)
rename src/backend/core/{ => capabilities}/plugins/packaging/skill_transform.py (96%)
rename src/backend/core/{ => capabilities}/plugins/packaging/sources.py (83%)
create mode 100644 src/backend/core/capabilities/plugins/resources/__init__.py
rename src/backend/core/{ => capabilities}/plugins/resources/artifacts.py (93%)
rename src/backend/core/{ => capabilities}/plugins/resources/authentication.py (93%)
rename src/backend/core/{ => capabilities}/plugins/resources/configuration.py (100%)
rename src/backend/core/{ => capabilities}/plugins/resources/confinement.py (83%)
rename src/backend/core/{ => capabilities}/plugins/resources/crypto.py (100%)
create mode 100644 src/backend/core/capabilities/plugins/resources/deadlines.py
rename src/backend/core/{ => capabilities}/plugins/resources/installation.py (56%)
rename src/backend/core/{ => capabilities}/plugins/resources/lifecycle.py (59%)
create mode 100644 src/backend/core/capabilities/plugins/resources/owners.py
create mode 100644 src/backend/core/capabilities/plugins/resources/prewarm.py
create mode 100644 src/backend/core/capabilities/plugins/resources/reservation.py
create mode 100644 src/backend/core/capabilities/plugins/resources/service.py
rename src/backend/core/{ => capabilities}/plugins/resources/store.py (97%)
rename src/backend/core/{ => capabilities}/plugins/resources/stream.py (72%)
rename src/backend/core/{ => capabilities}/plugins/resources/transport.py (100%)
rename src/backend/core/{ => capabilities}/plugins/runtime/__init__.py (76%)
rename src/backend/core/{ => capabilities}/plugins/runtime/activation_history.py (97%)
rename src/backend/core/{ => capabilities}/plugins/runtime/models.py (100%)
rename src/backend/core/{ => capabilities}/plugins/runtime/resolution.py (91%)
rename src/backend/core/{ => capabilities}/plugins/runtime/sticky_selection.py (85%)
rename src/backend/core/capabilities/{ref.py => reference.py} (98%)
delete mode 100644 src/backend/core/capabilities/runtime/__init__.py
delete mode 100644 src/backend/core/capabilities/skills.py
rename src/backend/core/{agent_skills => capabilities/skills}/__init__.py (97%)
create mode 100644 src/backend/core/capabilities/skills/backends/__init__.py
rename src/backend/core/{agent_skills => capabilities/skills}/backends/composite.py (97%)
rename src/backend/core/{agent_skills => capabilities/skills}/backends/database.py (99%)
rename src/backend/core/{agent_skills => capabilities/skills}/backends/filesystem.py (98%)
rename src/backend/core/{agent_skills => capabilities/skills}/backends/protocol.py (100%)
rename src/backend/core/{agent_skills => capabilities/skills}/binary_files.py (100%)
create mode 100644 src/backend/core/capabilities/skills/cache_refresh.py
rename src/backend/core/{agent_skills => capabilities/skills}/config.py (89%)
create mode 100644 src/backend/core/capabilities/skills/deps/__init__.py
rename src/backend/core/{services/skill_deps_aggregator.py => capabilities/skills/deps/aggregator.py} (92%)
rename src/backend/core/{agent_skills/deps_detector.py => capabilities/skills/deps/detector.py} (100%)
rename src/backend/core/{services/skill_deps_prober.py => capabilities/skills/deps/prober.py} (98%)
rename src/backend/core/{services/skill_deps_request_service.py => capabilities/skills/deps/requests.py} (81%)
rename src/backend/core/{services/skill_icon_service.py => capabilities/skills/icons.py} (100%)
create mode 100644 src/backend/core/capabilities/skills/installation.py
rename src/backend/core/{agent_skills => capabilities/skills}/loader.py (77%)
create mode 100644 src/backend/core/capabilities/skills/loader_factory.py
rename src/backend/core/{services/skill_management_service.py => capabilities/skills/management.py} (93%)
rename src/backend/core/{agent_skills => capabilities/skills}/publication.py (89%)
rename src/backend/core/{agent_skills => capabilities/skills}/registry.py (97%)
create mode 100644 src/backend/core/capabilities/skills/runtime_loader.py
create mode 100644 src/backend/core/capabilities/skills/script_discovery.py
rename src/backend/core/{agent_skills => capabilities/skills}/selection_record.py (98%)
rename src/backend/core/{agent_skills => capabilities/skills}/skill_archive.py (88%)
create mode 100644 src/backend/core/capabilities/skills/zip_reader.py
delete mode 100644 src/backend/core/channels/adapters/dingtalk.py
delete mode 100644 src/backend/core/channels/adapters/lark.py
delete mode 100644 src/backend/core/channels/inbound.py
rename src/backend/core/{services => chat}/audio_transcription.py (98%)
create mode 100644 src/backend/core/chat/compaction/__init__.py
rename src/backend/core/{services/context_budget_checkpoint.py => chat/compaction/budget_checkpoint.py} (97%)
rename src/backend/core/{services/compaction_coordinator.py => chat/compaction/coordinator.py} (90%)
rename src/backend/core/{services/compaction_history.py => chat/compaction/history.py} (93%)
create mode 100644 src/backend/core/chat/compaction/manual.py
rename src/backend/core/{services/compaction_progress.py => chat/compaction/progress.py} (100%)
rename src/backend/core/{services/compaction_service.py => chat/compaction/service.py} (96%)
rename src/backend/core/{services/compaction_summary.py => chat/compaction/summary.py} (93%)
rename src/backend/core/{services/context_usage_projection.py => chat/compaction/usage_projection.py} (97%)
create mode 100644 src/backend/core/chat/compaction_checkpoints.py
create mode 100644 src/backend/core/chat/compaction_snapshots.py
create mode 100644 src/backend/core/chat/compaction_types.py
rename src/backend/core/{services/chat_edition.py => chat/edition.py} (100%)
rename src/backend/{orchestration => core/chat}/followups.py (83%)
rename src/backend/core/{services/chat_fork_service.py => chat/fork.py} (99%)
create mode 100644 src/backend/core/chat/history_operations.py
create mode 100644 src/backend/core/chat/limits.py
rename src/backend/{orchestration => core/chat}/message_parser.py (97%)
create mode 100644 src/backend/core/chat/message_queries.py
create mode 100644 src/backend/core/chat/message_writes.py
create mode 100644 src/backend/core/chat/mode_seed.py
create mode 100644 src/backend/core/chat/mode_types.py
create mode 100644 src/backend/core/chat/mode_validation.py
rename src/backend/core/{services/chat_mode_service.py => chat/modes.py} (62%)
rename src/backend/core/{services/session_operations => chat/operations}/__init__.py (100%)
create mode 100644 src/backend/core/chat/operations/admission.py
rename src/backend/core/{services/session_operations => chat/operations}/admission_errors.py (92%)
rename src/backend/core/{services/session_operations => chat/operations}/agent_targets.py (94%)
rename src/backend/core/{services/session_operations => chat/operations}/commands.py (82%)
rename src/backend/core/{services/session_operations => chat/operations}/history.py (92%)
rename src/backend/core/{services/session_operations => chat/operations}/invocation.py (85%)
rename src/backend/core/{services/session_operations => chat/operations}/projects.py (90%)
rename src/backend/core/{services/session_operations => chat/operations}/request_context.py (91%)
rename src/backend/core/{services/session_operations => chat/operations}/runs.py (90%)
rename src/backend/core/{services/session_operations => chat/operations}/schemas.py (92%)
rename src/backend/core/{services/session_operations => chat/operations}/session_context.py (69%)
rename src/backend/core/{services/session_operations => chat/operations}/sessions.py (86%)
rename src/backend/core/{services/plan_service.py => chat/plans.py} (100%)
rename src/backend/core/{services => chat}/project_init.py (91%)
create mode 100644 src/backend/core/chat/projection/__init__.py
create mode 100644 src/backend/core/chat/projection/codec.py
rename src/backend/{orchestration/run_projection.py => core/chat/projection/reducer.py} (62%)
rename src/backend/{orchestration/run_projection_storage.py => core/chat/projection/storage.py} (89%)
rename src/backend/{orchestration/run_subscription.py => core/chat/projection/subscription.py} (80%)
rename src/backend/core/{services/public_chat_service.py => chat/public.py} (78%)
rename src/backend/core/{services/chat_reference_service.py => chat/references.py} (98%)
create mode 100644 src/backend/core/chat/runtime/__init__.py
create mode 100644 src/backend/core/chat/runtime/admission.py
create mode 100644 src/backend/core/chat/runtime/chunks.py
rename src/backend/{orchestration/post_turn_compaction.py => core/chat/runtime/compaction.py} (84%)
create mode 100644 src/backend/core/chat/runtime/completion.py
create mode 100644 src/backend/core/chat/runtime/followups.py
create mode 100644 src/backend/core/chat/runtime/handoff.py
rename src/backend/{orchestration => core/chat/runtime}/job_wakeup.py (64%)
create mode 100644 src/backend/core/chat/runtime/pauses.py
create mode 100644 src/backend/core/chat/runtime/recovery.py
create mode 100644 src/backend/core/chat/runtime/recovery_snapshot.py
create mode 100644 src/backend/core/chat/runtime/snapshots.py
create mode 100644 src/backend/core/chat/runtime/steering.py
create mode 100644 src/backend/core/chat/runtime/turn_state.py
create mode 100644 src/backend/core/chat/runtime/worker.py
rename src/backend/core/{services/chat_sequencer.py => chat/sequencer.py} (92%)
create mode 100644 src/backend/core/chat/service.py
create mode 100644 src/backend/core/chat/session_operations.py
rename src/backend/core/{services => chat}/sidebar_status.py (100%)
rename src/backend/core/{services/chat_steer_service.py => chat/steer.py} (95%)
create mode 100644 src/backend/core/chat/steer_hook.py
rename src/backend/core/{services => chat}/steer_queue.py (100%)
rename src/backend/core/{services/subagent_routing_service.py => chat/subagent_routing.py} (100%)
rename src/backend/core/{services/chat_titles.py => chat/titles.py} (92%)
rename src/backend/{orchestration => core/chat}/tool_payloads.py (76%)
rename src/backend/core/config/{edition_display_names.py => edition.py} (78%)
create mode 100644 src/backend/core/config/store/__init__.py
create mode 100644 src/backend/core/config/store/config_keys.py
create mode 100644 src/backend/core/config/store/execution_options.py
create mode 100644 src/backend/core/config/store/runtime_env.py
create mode 100644 src/backend/core/config/store/system_config.py
create mode 100644 src/backend/core/config/store/system_config_constants.py
delete mode 100644 src/backend/core/content/content_blocks.py
delete mode 100644 src/backend/core/content/file_parser.py
create mode 100644 src/backend/core/db/artifact_scope.py
create mode 100644 src/backend/core/db/edition.py
delete mode 100644 src/backend/core/db/models/channel_relay.py
create mode 100644 src/backend/core/db/models/chat_resources.py
create mode 100644 src/backend/core/db/models/chat_runs.py
create mode 100644 src/backend/core/db/models/column_types.py
delete mode 100644 src/backend/core/db/models/kb_wiki.py
delete mode 100644 src/backend/core/db/models/observability.py
rename src/backend/core/db/{model_extensions.py => personal_model_fields.py} (100%)
rename src/backend/core/db/{edition_tables.py => personal_schema.py} (97%)
create mode 100644 src/backend/core/db/repository/folder.py
rename src/backend/core/db/repository/{agent.py => personal_agent.py} (100%)
rename src/backend/core/db/repository/{audit.py => personal_audit.py} (96%)
create mode 100644 src/backend/core/db/system_configuration.py
create mode 100644 src/backend/core/db/threads.py
create mode 100644 src/backend/core/db/user_cleanup.py
delete mode 100644 src/backend/core/evolution/activation.py
delete mode 100644 src/backend/core/evolution/agent_profile.py
create mode 100644 src/backend/core/evolution/assessment/__init__.py
rename src/backend/core/evolution/{ => assessment}/applicability.py (98%)
rename src/backend/core/evolution/{ => assessment}/benchmark.py (98%)
rename src/backend/core/evolution/{ => assessment}/evaluator.py (100%)
rename src/backend/core/evolution/{ => assessment}/experiments.py (99%)
rename src/backend/core/evolution/{ => assessment}/fault_injection.py (98%)
rename src/backend/core/evolution/{ => assessment}/metrics.py (100%)
rename src/backend/core/evolution/{ => assessment}/similarity.py (98%)
rename src/backend/core/evolution/{ => assessment}/sop_judge.py (98%)
create mode 100644 src/backend/core/evolution/cycle/__init__.py
create mode 100644 src/backend/core/evolution/cycle/activation.py
create mode 100644 src/backend/core/evolution/cycle/activation_constants.py
create mode 100644 src/backend/core/evolution/cycle/activation_policy.py
create mode 100644 src/backend/core/evolution/cycle/activation_types.py
create mode 100644 src/backend/core/evolution/cycle/candidate_records.py
create mode 100644 src/backend/core/evolution/cycle/composition_candidates.py
create mode 100644 src/backend/core/evolution/cycle/cycle_extras.py
create mode 100644 src/backend/core/evolution/cycle/cycle_extras_constants.py
create mode 100644 src/backend/core/evolution/cycle/episode_views.py
create mode 100644 src/backend/core/evolution/cycle/evaluation.py
create mode 100644 src/backend/core/evolution/cycle/extra_findings.py
create mode 100644 src/backend/core/evolution/cycle/graph_candidates.py
create mode 100644 src/backend/core/evolution/cycle/loop.py
create mode 100644 src/backend/core/evolution/cycle/memory_candidates.py
create mode 100644 src/backend/core/evolution/cycle/memory_findings.py
create mode 100644 src/backend/core/evolution/cycle/ordering.py
create mode 100644 src/backend/core/evolution/cycle/patterns.py
create mode 100644 src/backend/core/evolution/cycle/profile_release.py
create mode 100644 src/backend/core/evolution/cycle/promotion.py
create mode 100644 src/backend/core/evolution/cycle/promotion_constants.py
create mode 100644 src/backend/core/evolution/cycle/promotion_types.py
create mode 100644 src/backend/core/evolution/cycle/prompt_candidates.py
rename src/backend/core/evolution/{ => cycle}/readiness.py (93%)
rename src/backend/core/evolution/{ => cycle}/release.py (100%)
create mode 100644 src/backend/core/evolution/cycle/retirement.py
rename src/backend/core/evolution/{ => cycle}/runtime_binding.py (87%)
create mode 100644 src/backend/core/evolution/cycle/skill_document.py
create mode 100644 src/backend/core/evolution/cycle/skill_release.py
rename src/backend/core/evolution/{ => cycle}/timescales.py (99%)
rename src/backend/core/evolution/{ => cycle}/trigger.py (81%)
create mode 100644 src/backend/core/evolution/cycle/usage_evidence.py
delete mode 100644 src/backend/core/evolution/cycle_extras.py
create mode 100644 src/backend/core/evolution/evidence/__init__.py
rename src/backend/core/evolution/{ => evidence}/cassette.py (94%)
rename src/backend/core/evolution/{ => evidence}/counterfactual.py (99%)
rename src/backend/core/evolution/{ => evidence}/credit.py (99%)
rename src/backend/core/evolution/{ => evidence}/events.py (99%)
rename src/backend/core/evolution/{ => evidence}/evidence_contract.py (100%)
rename src/backend/core/evolution/{ => evidence}/evidence_resolver.py (98%)
rename src/backend/core/evolution/{ => evidence}/exposure.py (95%)
rename src/backend/core/evolution/{ => evidence}/lineage.py (99%)
rename src/backend/core/evolution/{ => evidence}/outcomes.py (100%)
rename src/backend/core/evolution/{ => evidence}/replay.py (100%)
rename src/backend/core/evolution/{ => evidence}/trace_assembler.py (98%)
rename src/backend/core/evolution/{ => evidence}/trace_store.py (97%)
create mode 100644 src/backend/core/evolution/generators/__init__.py
rename src/backend/core/evolution/{ => generators}/graph_scan.py (100%)
rename src/backend/core/evolution/{ => generators}/memory_apply.py (98%)
rename src/backend/core/evolution/{ => generators}/memory_ops.py (99%)
rename src/backend/core/evolution/{ => generators}/memory_scan.py (99%)
rename src/backend/core/evolution/{ => generators}/ontology_bridge.py (100%)
rename src/backend/core/evolution/{ => generators}/orchestration_gen.py (99%)
rename src/backend/core/evolution/{ => generators}/prompt_gen.py (100%)
rename src/backend/core/evolution/{ => generators}/skill_candidate_engine.py (96%)
create mode 100644 src/backend/core/evolution/generators/skill_drafting.py
create mode 100644 src/backend/core/evolution/generators/skill_evidence.py
create mode 100644 src/backend/core/evolution/generators/skill_gen.py
create mode 100644 src/backend/core/evolution/generators/skill_gen_constants.py
delete mode 100644 src/backend/core/evolution/loop.py
create mode 100644 src/backend/core/evolution/profiles/__init__.py
create mode 100644 src/backend/core/evolution/profiles/agent_profile.py
create mode 100644 src/backend/core/evolution/profiles/agent_profile_constants.py
create mode 100644 src/backend/core/evolution/profiles/change_preview.py
rename src/backend/core/evolution/{ => profiles}/console_view.py (99%)
rename src/backend/core/evolution/{ => profiles}/federation.py (99%)
rename src/backend/core/evolution/{ => profiles}/personal.py (88%)
rename src/backend/core/evolution/{user_settings.py => profiles/personal_review.py} (60%)
rename src/backend/core/evolution/{ => profiles}/policies.py (88%)
rename src/backend/core/evolution/{ => profiles}/prefs.py (100%)
create mode 100644 src/backend/core/evolution/profiles/profile_selection.py
create mode 100644 src/backend/core/evolution/profiles/stall_hook.py
create mode 100644 src/backend/core/evolution/profiles/user_settings.py
create mode 100644 src/backend/core/evolution/profiles/user_settings_constants.py
delete mode 100644 src/backend/core/evolution/promotion.py
create mode 100644 src/backend/core/evolution/settlement/__init__.py
create mode 100644 src/backend/core/evolution/settlement/memory_service.py
rename src/backend/core/evolution/{ => settlement}/settlement.py (100%)
rename src/backend/core/evolution/{ => settlement}/settlement_runner.py (88%)
rename src/backend/core/evolution/{ => settlement}/settlement_store.py (98%)
delete mode 100644 src/backend/core/evolution/skill_gen.py
delete mode 100644 src/backend/core/extensions/__init__.py
delete mode 100644 src/backend/core/extensions/registry.py
create mode 100644 src/backend/core/files/__init__.py
create mode 100644 src/backend/core/files/artifact_bytes.py
rename src/backend/core/{ => files}/artifacts/__init__.py (96%)
rename src/backend/core/{services/artifact_backfill.py => files/artifacts/backfill.py} (95%)
create mode 100644 src/backend/core/files/artifacts/generated.py
rename src/backend/core/{content/artifact_key_repair.py => files/artifacts/key_repair.py} (100%)
rename src/backend/core/{ => files}/artifacts/local_reference.py (89%)
rename src/backend/core/{services/artifact_location.py => files/artifacts/location.py} (52%)
rename src/backend/core/{content/artifact_reader.py => files/artifacts/reader.py} (91%)
rename src/backend/core/{content/artifact_refs.py => files/artifacts/refs.py} (93%)
rename src/backend/core/{services/artifact_service.py => files/artifacts/service.py} (93%)
rename src/backend/core/{ => files}/artifacts/store.py (96%)
rename src/backend/core/{content/artifact_summary.py => files/artifacts/summary.py} (97%)
rename src/backend/core/{services/artifact_text_service.py => files/artifacts/text.py} (92%)
create mode 100644 src/backend/core/files/content/__init__.py
rename src/backend/core/{ => files}/content/archive_browser.py (99%)
rename src/backend/core/{ => files}/content/archive_limits.py (100%)
create mode 100644 src/backend/core/files/content/branding.py
create mode 100644 src/backend/core/files/content/content_blocks.py
create mode 100644 src/backend/core/files/content/content_blocks_constants.py
create mode 100644 src/backend/core/files/content/file_formats.py
create mode 100644 src/backend/core/files/content/file_parser.py
rename src/backend/core/{ => files}/content/file_validation.py (100%)
rename src/backend/core/{ => files}/content/office.py (100%)
rename src/backend/core/{ => files}/content/office_preview_cache.py (98%)
create mode 100644 src/backend/core/files/content/parser_config.py
create mode 100644 src/backend/core/files/content/pdf_parser.py
create mode 100644 src/backend/core/files/content/presentation_parser.py
create mode 100644 src/backend/core/files/content/spreadsheet_parser.py
rename src/backend/core/{ => files}/content/svg_fit.py (97%)
create mode 100644 src/backend/core/files/content/text_parser.py
create mode 100644 src/backend/core/files/content/word_parser.py
create mode 100644 src/backend/core/files/edition.py
rename src/backend/core/{services => files}/folder_batch.py (69%)
rename src/backend/core/{services/personal_file_operations.py => files/personal_operations.py} (86%)
rename src/backend/core/{ => files}/spaces/__init__.py (100%)
rename src/backend/core/{ => files}/spaces/access.py (95%)
rename src/backend/core/{ => files}/spaces/files.py (96%)
rename src/backend/core/{ => files}/spaces/leases.py (86%)
rename src/backend/core/{ => files}/spaces/mounts.py (78%)
rename src/backend/core/{ => files}/spaces/projects.py (94%)
rename src/backend/core/{ => files}/spaces/recovery.py (94%)
rename src/backend/core/{ => files}/spaces/sandbox.py (73%)
rename src/backend/core/{ => files}/spaces/search.py (97%)
rename src/backend/core/{ => files}/spaces/text.py (92%)
rename src/backend/core/{ => files}/spaces/tree.py (100%)
rename src/backend/core/{ => files}/storage/__init__.py (56%)
rename src/backend/core/{ => files}/storage/entries.py (100%)
rename src/backend/core/{ => files}/storage/factory.py (81%)
rename src/backend/core/{ => files}/storage/local.py (96%)
rename src/backend/core/{ => files}/storage/local_directory.py (94%)
rename src/backend/core/{ => files}/storage/local_paths.py (98%)
rename src/backend/core/{ => files}/storage/protocol.py (100%)
rename src/backend/core/{ => files}/storage/s3.py (98%)
rename src/backend/core/{ => files}/storage/s3_directory.py (96%)
rename src/backend/core/{services => files}/upload_retry.py (92%)
rename src/backend/core/{capabilities/errors.py => harness/capability_errors.py} (100%)
create mode 100644 src/backend/core/harness/context_items.py
rename src/backend/core/{llm => harness}/execution_manifest.py (99%)
rename src/backend/core/{services/harness_ledger.py => harness/ledger.py} (53%)
create mode 100644 src/backend/core/harness/log_audit.py
create mode 100644 src/backend/core/harness/log_context.py
create mode 100644 src/backend/core/harness/log_records.py
create mode 100644 src/backend/core/harness/log_writer.py
create mode 100644 src/backend/core/harness/log_writer_constants.py
create mode 100644 src/backend/core/harness/observation_writer.py
create mode 100644 src/backend/core/harness/registry/__init__.py
create mode 100644 src/backend/core/harness/registry/authentication.py
create mode 100644 src/backend/core/harness/registry/build_governance.py
create mode 100644 src/backend/core/harness/registry/cache_invalidation.py
create mode 100644 src/backend/core/harness/registry/capability_management.py
create mode 100644 src/backend/core/harness/registry/capability_revisions.py
create mode 100644 src/backend/core/harness/registry/capability_runtime.py
create mode 100644 src/backend/core/harness/registry/catalog_options.py
create mode 100644 src/backend/core/harness/registry/chat_execution.py
create mode 100644 src/backend/core/harness/registry/chat_history.py
create mode 100644 src/backend/core/harness/registry/compaction.py
create mode 100644 src/backend/core/harness/registry/device_catalog.py
rename src/backend/core/{extensions => harness/registry}/edition.py (100%)
create mode 100644 src/backend/core/harness/registry/evaluation.py
create mode 100644 src/backend/core/harness/registry/execution_account.py
create mode 100644 src/backend/core/harness/registry/execution_catalog.py
create mode 100644 src/backend/core/harness/registry/execution_identity.py
create mode 100644 src/backend/core/harness/registry/hook_factories.py
create mode 100644 src/backend/core/harness/registry/host_runtime.py
create mode 100644 src/backend/core/harness/registry/job_operations.py
create mode 100644 src/backend/core/harness/registry/local_files.py
create mode 100644 src/backend/core/harness/registry/local_publication.py
create mode 100644 src/backend/core/harness/registry/marketplace_catalog.py
create mode 100644 src/backend/core/harness/registry/mcp_clients.py
create mode 100644 src/backend/core/harness/registry/model_credentials.py
create mode 100644 src/backend/core/harness/registry/model_services.py
create mode 100644 src/backend/core/harness/registry/package_sources.py
create mode 100644 src/backend/core/harness/registry/package_views.py
create mode 100644 src/backend/core/harness/registry/registry.py
create mode 100644 src/backend/core/harness/registry/resource_owners.py
create mode 100644 src/backend/core/harness/registry/retrieval_context.py
create mode 100644 src/backend/core/harness/registry/run_cancellation.py
create mode 100644 src/backend/core/harness/registry/run_capabilities.py
create mode 100644 src/backend/core/harness/registry/schema_lifecycle.py
create mode 100644 src/backend/core/harness/registry/skill_content.py
create mode 100644 src/backend/core/harness/registry/system_configuration.py
create mode 100644 src/backend/core/harness/registry/tool_operations.py
rename src/backend/core/{services/desktop_gateway_uploads.py => harness/registry/transfer_channels.py} (53%)
create mode 100644 src/backend/core/harness/registry/user_cleanup.py
create mode 100644 src/backend/core/harness/tool_call_context.py
create mode 100644 src/backend/core/harness/tool_effect_context.py
create mode 100644 src/backend/core/infra/byte_range.py
rename src/backend/core/{capabilities/junction.py => infra/directory_links.py} (92%)
create mode 100644 src/backend/core/infra/failures.py
rename src/backend/core/{capabilities/lockfile.py => infra/file_lock.py} (100%)
rename src/backend/core/{ => infra}/immutable.py (100%)
create mode 100644 src/backend/core/infra/json_output.py
create mode 100644 src/backend/core/infra/local_runtime/__init__.py
create mode 100644 src/backend/core/infra/local_runtime/dynamic_servers.py
rename src/backend/{orchestration/local_sidecar_lifecycle.py => core/infra/local_runtime/lifecycle.py} (89%)
rename src/backend/core/{sandbox/providers/script_runner/auth.py => infra/local_runtime/runner_auth.py} (100%)
rename src/backend/{orchestration/local_subprocess.py => core/infra/local_runtime/startup.py} (87%)
create mode 100644 src/backend/core/infra/model_output.py
create mode 100644 src/backend/core/infra/probes/__init__.py
rename src/backend/core/{services/edition_service_probe.py => infra/probes/edition.py} (100%)
rename src/backend/core/{services => infra/probes}/service_probes.py (83%)
delete mode 100644 src/backend/core/infra/rate_limit.py
rename src/backend/core/{chat/display_bounds.py => infra/text_bounds.py} (100%)
rename src/backend/{orchestration => core}/jobs/__init__.py (100%)
rename src/backend/{orchestration => core}/jobs/agent.py (94%)
rename src/backend/{orchestration => core}/jobs/callback.py (100%)
rename src/backend/{orchestration => core}/jobs/files.py (95%)
rename src/backend/{orchestration => core}/jobs/notifications.py (81%)
rename src/backend/{orchestration => core}/jobs/owner.py (62%)
rename src/backend/{orchestration => core}/jobs/process.py (95%)
create mode 100644 src/backend/core/jobs/reminder_hook.py
rename src/backend/{orchestration => core}/jobs/runner.py (100%)
rename src/backend/{orchestration/job_runtime.py => core/jobs/runtime.py} (85%)
rename src/backend/{orchestration => core}/jobs/sdk.py (100%)
rename src/backend/{orchestration => core}/jobs/state.py (98%)
rename src/backend/core/{services/job_service.py => jobs/store.py} (100%)
rename src/backend/{orchestration => core}/jobs/supervisor.py (88%)
create mode 100644 src/backend/core/jobs/tool_operations.py
delete mode 100644 src/backend/core/kb/kb_assets.py
delete mode 100644 src/backend/core/kb/kb_parser.py
delete mode 100644 src/backend/core/kb/kb_vector.py
delete mode 100644 src/backend/core/kb/wiki/local_provider.py
delete mode 100644 src/backend/core/llm/_distill_shared.py
create mode 100644 src/backend/core/llm/api_runtime/__init__.py
rename src/backend/core/llm/{agent_api_runtime.py => api_runtime/agent.py} (97%)
rename src/backend/core/llm/{agent_api_skills.py => api_runtime/agent_skills.py} (96%)
rename src/backend/core/llm/{agent_api_tools.py => api_runtime/agent_tools.py} (90%)
rename src/backend/core/llm/{project_api_runtime.py => api_runtime/project.py} (94%)
delete mode 100644 src/backend/core/llm/capability_tools.py
create mode 100644 src/backend/core/llm/citations/__init__.py
create mode 100644 src/backend/core/llm/citations/allocator.py
create mode 100644 src/backend/core/llm/citations/anchor.py
create mode 100644 src/backend/core/llm/citations/anchor_state.py
create mode 100644 src/backend/core/llm/citations/extraction.py
create mode 100644 src/backend/core/llm/citations/hook.py
create mode 100644 src/backend/core/llm/citations/types.py
delete mode 100644 src/backend/core/llm/command_middleware.py
create mode 100644 src/backend/core/llm/context/__init__.py
create mode 100644 src/backend/core/llm/context/adapter.py
create mode 100644 src/backend/core/llm/context/attachment_hook.py
rename src/backend/core/{services/compaction_budget.py => llm/context/budget.py} (98%)
rename src/backend/core/llm/{ => context}/compacting_agent.py (96%)
rename src/backend/core/llm/{ => context}/compaction.py (98%)
create mode 100644 src/backend/core/llm/context/content.py
create mode 100644 src/backend/core/llm/context/ir.py
create mode 100644 src/backend/core/llm/context/ir_constants.py
create mode 100644 src/backend/core/llm/context/items.py
create mode 100644 src/backend/core/llm/context/message_defaults.py
rename src/backend/core/llm/{ => context}/offloader.py (96%)
create mode 100644 src/backend/core/llm/context/provider_conversion.py
create mode 100644 src/backend/core/llm/context/reasoning_hooks.py
create mode 100644 src/backend/core/llm/context/reference_conversion.py
create mode 100644 src/backend/core/llm/context/rendering.py
rename src/backend/core/llm/{context_safety.py => context/safety.py} (87%)
rename src/backend/core/llm/{ => context}/system_reminder.py (94%)
rename src/backend/core/llm/{context_usage.py => context/usage.py} (99%)
create mode 100644 src/backend/core/llm/context/workspace_hooks.py
delete mode 100644 src/backend/core/llm/context_adapter.py
delete mode 100644 src/backend/core/llm/context_ir.py
rename src/backend/core/llm/factory/{selection/device_bindings.py => runtime/capability_bindings.py} (93%)
rename src/backend/{orchestration/registry.py => core/llm/factory/specs.py} (99%)
create mode 100644 src/backend/core/llm/factory/tools/definitions.py
delete mode 100644 src/backend/core/llm/finish_guard.py
delete mode 100644 src/backend/core/llm/hooks.py
create mode 100644 src/backend/core/llm/host_services.py
delete mode 100644 src/backend/core/llm/management_tool.py
create mode 100644 src/backend/core/llm/mcp/__init__.py
create mode 100644 src/backend/core/llm/mcp/callbacks.py
create mode 100644 src/backend/core/llm/mcp/chat_sessions.py
create mode 100644 src/backend/core/llm/mcp/client.py
create mode 100644 src/backend/core/llm/mcp/context_tools.py
create mode 100644 src/backend/core/llm/mcp/deadline.py
rename src/backend/core/llm/{mcp_invocation.py => mcp/invocation.py} (87%)
create mode 100644 src/backend/core/llm/mcp/lifetime.py
create mode 100644 src/backend/core/llm/mcp/manager.py
create mode 100644 src/backend/core/llm/mcp/results.py
create mode 100644 src/backend/core/llm/mcp/session.py
create mode 100644 src/backend/core/llm/mcp/tools.py
delete mode 100644 src/backend/core/llm/mcp_manager.py
delete mode 100644 src/backend/core/llm/mcp_pool.py
create mode 100644 src/backend/core/llm/middleware/__init__.py
rename src/backend/core/llm/{middleware_migration.py => middleware/migration_map.py} (51%)
delete mode 100644 src/backend/core/llm/middlewares.py
delete mode 100644 src/backend/core/llm/persona_distiller.py
create mode 100644 src/backend/core/llm/ports.py
delete mode 100644 src/backend/core/llm/project_environment_middleware.py
create mode 100644 src/backend/core/llm/prompt_files.py
create mode 100644 src/backend/core/llm/prompt_kinds.py
create mode 100644 src/backend/core/llm/prompt_rendering.py
create mode 100644 src/backend/core/llm/prompt_storage.py
create mode 100644 src/backend/core/llm/prompt_versions.py
create mode 100644 src/backend/core/llm/prompt_versions_state.py
delete mode 100644 src/backend/core/llm/providers/__init__.py
create mode 100644 src/backend/core/llm/runtime/__init__.py
rename src/backend/core/llm/{ => runtime}/agentscope_hook_adapter.py (63%)
create mode 100644 src/backend/core/llm/runtime/context_hook_adapter.py
create mode 100644 src/backend/core/llm/runtime/delta_coalescer.py
create mode 100644 src/backend/core/llm/runtime/file_context.py
create mode 100644 src/backend/core/llm/runtime/finish_guard.py
create mode 100644 src/backend/core/llm/runtime/hooks_constants.py
rename src/backend/core/llm/{ => runtime}/human_interaction.py (86%)
rename src/backend/core/llm/{ => runtime}/interaction_store.py (100%)
rename src/backend/core/llm/{ => runtime}/manifest_agent.py (93%)
rename src/backend/core/llm/{ => runtime}/message_compat.py (93%)
create mode 100644 src/backend/core/llm/runtime/model_cache.py
create mode 100644 src/backend/core/llm/runtime/model_selection_adapter.py
rename src/backend/core/llm/{ => runtime}/model_steps.py (98%)
rename src/backend/core/llm/{ => runtime}/project_environment.py (88%)
create mode 100644 src/backend/core/llm/runtime/project_environment_hook.py
create mode 100644 src/backend/core/llm/runtime/review_text_agent.py
rename src/backend/core/llm/{ => runtime}/session_capabilities.py (97%)
create mode 100644 src/backend/core/llm/runtime/state.py
create mode 100644 src/backend/core/llm/runtime/stream_events.py
create mode 100644 src/backend/core/llm/runtime/stream_loop.py
create mode 100644 src/backend/core/llm/runtime/stream_text.py
create mode 100644 src/backend/core/llm/runtime/streaming.py
create mode 100644 src/backend/core/llm/runtime/streaming_constants.py
rename src/backend/{orchestration => core/llm/runtime}/tool_callbacks.py (70%)
create mode 100644 src/backend/core/llm/runtime/tool_execution_adapter.py
create mode 100644 src/backend/core/llm/runtime/tool_hook_adapter.py
rename src/backend/core/llm/{ => runtime}/tool_media_store.py (95%)
create mode 100644 src/backend/core/llm/runtime/tool_result_adapter.py
create mode 100644 src/backend/core/llm/runtime/turn_state.py
rename src/backend/core/llm/{ => runtime}/workspace.py (100%)
rename src/backend/core/{agent_skills/selector.py => llm/skill_selection.py} (91%)
delete mode 100644 src/backend/core/llm/steer_middleware.py
create mode 100644 src/backend/core/llm/subagents/__init__.py
rename src/backend/core/llm/{_subagent_execution.py => subagents/execution.py} (90%)
rename src/backend/core/llm/{_subagent_prompts.py => subagents/prompts.py} (100%)
rename src/backend/core/llm/{subagent_sessions.py => subagents/sessions.py} (98%)
rename src/backend/core/llm/{_subagent_stream.py => subagents/stream.py} (80%)
rename src/backend/core/llm/{subagent_tool.py => subagents/tool.py} (89%)
delete mode 100644 src/backend/core/llm/tool_collector.py
create mode 100644 src/backend/core/llm/tool_permissions/hook.py
delete mode 100644 src/backend/core/llm/tool_permissions/middleware.py
rename src/backend/core/llm/{gateway_mcp_tool.py => tools/cloud_gateway.py} (71%)
create mode 100644 src/backend/core/llm/tools/cloud_manifest.py
rename src/backend/core/llm/tools/{_tool_confirmation => confirmation}/__init__.py (100%)
rename src/backend/core/llm/tools/{_tool_confirmation => confirmation}/gate.py (99%)
rename src/backend/core/llm/tools/{_tool_confirmation => confirmation}/pending.py (96%)
rename src/backend/core/llm/tools/{_tool_confirmation => confirmation}/shared.py (98%)
rename src/backend/core/llm/tools/{_tool_confirmation => confirmation}/state.py (89%)
delete mode 100644 src/backend/core/llm/tools/data_context_tool.py
create mode 100644 src/backend/core/llm/tools/management.py
rename src/backend/core/{ontology/toolkit.py => llm/tools/ontology_toolkit.py} (85%)
rename src/backend/core/llm/{plan_update_tool.py => tools/plan_update.py} (92%)
rename src/backend/core/{plugins/runtime/activation.py => llm/tools/plugin_activation.py} (78%)
create mode 100644 src/backend/core/memory/edition.py
create mode 100644 src/backend/core/memory/graph_contract.py
create mode 100644 src/backend/core/memory/graph_state.py
rename src/backend/core/{services/memory_settings_service.py => memory/settings.py} (98%)
create mode 100644 src/backend/core/memory/settlement_port.py
create mode 100644 src/backend/core/models/__init__.py
create mode 100644 src/backend/core/models/admission.py
create mode 100644 src/backend/core/models/api_protocol_backfill.py
rename src/backend/core/{llm => models}/chat_models.py (92%)
create mode 100644 src/backend/core/models/completions/__init__.py
rename src/backend/core/{llm => models/completions}/classifier.py (84%)
rename src/backend/core/{llm => models/completions}/single_turn.py (96%)
rename src/backend/core/{llm => models/completions}/summarizer.py (86%)
rename src/backend/core/{services/model_config.py => models/config.py} (59%)
create mode 100644 src/backend/core/models/constants.py
rename src/backend/core/{llm/context_manager.py => models/context_window.py} (97%)
create mode 100644 src/backend/core/models/embedding.py
create mode 100644 src/backend/core/models/embedding_state.py
rename src/backend/core/{llm => models}/failover.py (94%)
rename src/backend/core/{db/model_import_validation.py => models/import_validation.py} (93%)
create mode 100644 src/backend/core/models/providers/__init__.py
rename src/backend/core/{llm => models}/providers/_image_tokens.py (96%)
rename src/backend/core/{llm => models}/providers/context_probe.py (99%)
rename src/backend/core/{llm => models}/providers/openai_compat.py (96%)
rename src/backend/core/{llm => models}/providers/protocol_probe.py (100%)
rename src/backend/core/{llm => models}/providers/reasoning_probe.py (97%)
rename src/backend/core/{llm => models}/providers/registry.py (99%)
rename src/backend/core/{llm => models}/providers/stream_retry.py (96%)
rename src/backend/core/{llm => models}/providers/vendor_models.py (97%)
rename src/backend/core/{llm => models}/reasoning_effort.py (100%)
rename src/backend/core/{llm => models}/reasoning_replay.py (100%)
rename src/backend/core/{db/model_repository.py => models/repository.py} (99%)
create mode 100644 src/backend/core/models/reranking.py
rename src/backend/core/{llm/responses_models.py => models/responses.py} (80%)
rename src/backend/core/{services/user_model_selection.py => models/selection.py} (63%)
create mode 100644 src/backend/core/models/selection_hook.py
rename src/backend/core/{llm => models}/tool_call_identity.py (100%)
rename src/backend/core/{llm => models}/tool_result_media.py (69%)
rename src/backend/core/{llm/model_usage.py => models/usage.py} (98%)
create mode 100644 src/backend/core/models/vision/__init__.py
rename src/backend/core/{ => models}/vision/attachments.py (83%)
rename src/backend/core/{ => models}/vision/prompt.py (98%)
rename src/backend/core/{ => models}/vision/provider.py (97%)
rename src/backend/core/{ => models}/vision/render.py (98%)
rename src/backend/core/{ => models}/vision/schema.py (100%)
rename src/backend/core/{ => models}/vision/service.py (96%)
delete mode 100644 src/backend/core/myspace/duplicate_cleanup.py
create mode 100644 src/backend/core/ontology/audit_events.py
rename src/backend/core/{services/ontology_evolution_service.py => ontology/evolution.py} (87%)
create mode 100644 src/backend/core/ontology/host.py
create mode 100644 src/backend/core/ontology/manual_review.py
create mode 100644 src/backend/core/ontology/output_review_state.py
rename src/backend/core/{services/ontology_policy.py => ontology/policy.py} (97%)
create mode 100644 src/backend/core/ontology/review_operations.py
create mode 100644 src/backend/core/ontology/review_prompt.py
create mode 100644 src/backend/core/ontology/reviewer.py
create mode 100644 src/backend/core/ontology/reviewer_state.py
create mode 100644 src/backend/core/ontology/runtime_gates.py
create mode 100644 src/backend/core/ontology/runtime_pack.py
create mode 100644 src/backend/core/ontology/runtime_policy.py
rename src/backend/core/{services/ontology_service.py => ontology/service.py} (68%)
create mode 100644 src/backend/core/ontology/tool_hook.py
create mode 100644 src/backend/core/ontology/validation_models.py
delete mode 100644 src/backend/core/plugins/README.md
delete mode 100644 src/backend/core/plugins/management/__init__.py
delete mode 100644 src/backend/core/plugins/resources/application.py
delete mode 100644 src/backend/core/plugins/resources/application_cleanup.py
delete mode 100644 src/backend/core/plugins/resources/deadlines.py
delete mode 100644 src/backend/core/plugins/resources/owners.py
delete mode 100644 src/backend/core/plugins/resources/prewarm.py
delete mode 100644 src/backend/core/plugins/resources/reservation.py
delete mode 100644 src/backend/core/plugins/resources/service.py
create mode 100644 src/backend/core/projects/__init__.py
rename src/backend/core/{services/project_access.py => projects/access.py} (90%)
rename src/backend/core/{services/project_api_service.py => projects/api.py} (97%)
rename src/backend/core/{services/project_service.py => projects/base.py} (85%)
create mode 100644 src/backend/core/projects/edition.py
rename src/backend/core/{project_environment => projects/environment}/__init__.py (100%)
rename src/backend/core/{project_environment => projects/environment}/config.py (100%)
rename src/backend/core/{project_environment => projects/environment}/dependencies.py (96%)
rename src/backend/core/{services/project_environment.py => projects/environment/service.py} (86%)
rename src/backend/core/{services/project_environment_resources.py => projects/environment_resources.py} (90%)
rename src/backend/core/{services/project_file_service.py => projects/files.py} (78%)
rename src/backend/core/{services/project_instructions.py => projects/instructions.py} (75%)
create mode 100644 src/backend/core/projects/local_resolve.py
rename src/backend/core/{services/local_project_workspace.py => projects/local_workspace.py} (97%)
rename src/backend/core/{services/project_scope.py => projects/personal_scope.py} (95%)
create mode 100644 src/backend/core/projects/personal_summary.py
rename src/backend/core/{services/project_transfer.py => projects/personal_transfer.py} (100%)
create mode 100644 src/backend/core/projects/project_names.py
rename src/backend/core/{services/project_source.py => projects/source.py} (91%)
rename src/backend/core/{services/user_folder_service.py => projects/user_folders.py} (95%)
create mode 100644 src/backend/core/runs/__init__.py
rename src/backend/{orchestration/agent_api_recovery.py => core/runs/api_recovery.py} (94%)
rename src/backend/core/{services/run_binding.py => runs/binding.py} (73%)
create mode 100644 src/backend/core/runs/clock.py
create mode 100644 src/backend/core/runs/events/__init__.py
create mode 100644 src/backend/core/runs/events/projection.py
rename src/backend/{orchestration/run_event_stream.py => core/runs/events/store.py} (84%)
create mode 100644 src/backend/core/runs/events/subscription.py
create mode 100644 src/backend/core/runs/events/writer.py
create mode 100644 src/backend/core/runs/host.py
rename src/backend/core/{services/run_journal.py => runs/journal.py} (94%)
rename src/backend/core/{services/run_journal_domain.py => runs/journal_domain.py} (100%)
rename src/backend/core/{services/run_journal_lifecycle.py => runs/journal_lifecycle.py} (93%)
rename src/backend/core/{services/run_journal_recovery.py => runs/journal_recovery.py} (97%)
create mode 100644 src/backend/core/runs/ownership.py
create mode 100644 src/backend/core/runs/runtime.py
create mode 100644 src/backend/core/runs/settings.py
create mode 100644 src/backend/core/runs/tasks.py
rename src/backend/{orchestration => core/runs}/tool_effect_recovery.py (68%)
create mode 100644 src/backend/core/runs/tool_effects/__init__.py
create mode 100644 src/backend/core/runs/tool_effects/arguments.py
create mode 100644 src/backend/core/runs/tool_effects/errors.py
create mode 100644 src/backend/core/runs/tool_effects/gateway.py
create mode 100644 src/backend/core/runs/tool_effects/hooks.py
create mode 100644 src/backend/core/runs/tool_effects/host.py
create mode 100644 src/backend/core/runs/tool_effects/intent_transactions.py
create mode 100644 src/backend/core/runs/tool_effects/invocation_claims.py
create mode 100644 src/backend/core/runs/tool_effects/journal.py
create mode 100644 src/backend/core/runs/tool_effects/journal_constants.py
create mode 100644 src/backend/core/runs/tool_effects/recovery.py
create mode 100644 src/backend/core/runs/tool_effects/recovery_claims.py
create mode 100644 src/backend/core/runs/tool_effects/registry.py
create mode 100644 src/backend/core/runs/tool_effects/result_transactions.py
create mode 100644 src/backend/core/runs/tool_effects/retention.py
create mode 100644 src/backend/core/runs/tool_effects/types.py
create mode 100644 src/backend/core/runs/watchdog.py
create mode 100644 src/backend/core/sandbox/commands/__init__.py
create mode 100644 src/backend/core/sandbox/commands/completion_hook.py
rename src/backend/core/{services => sandbox}/commands/context.py (85%)
rename src/backend/core/{services => sandbox}/commands/delivery.py (92%)
rename src/backend/{orchestration => core/sandbox}/commands/events.py (93%)
rename src/backend/{orchestration => core/sandbox}/commands/notifications.py (77%)
create mode 100644 src/backend/core/sandbox/commands/runtime.py
rename src/backend/core/{services => sandbox}/commands/store.py (95%)
rename src/backend/{orchestration => core/sandbox}/commands/subscription.py (66%)
create mode 100644 src/backend/core/sandbox/credential_dispatch.py
rename src/backend/core/{llm/tool_permissions => sandbox/permissions}/account.py (90%)
rename src/backend/core/{llm/tool_permissions/preferences.py => sandbox/permissions/approval.py} (94%)
rename src/backend/core/{llm/tool_permissions/models.py => sandbox/permissions/dispatch_models.py} (98%)
rename src/backend/core/{llm/tool_permissions/specs.py => sandbox/permissions/tool_specs.py} (95%)
create mode 100644 src/backend/core/sandbox/permissions/vocabulary.py
delete mode 100644 src/backend/core/sandbox/providers/opensandbox/account_volumes.py
create mode 100644 src/backend/core/sandbox/runner/user_env.py
rename src/backend/core/{llm/tools/_paths.py => sandbox/workspace/resolution.py} (88%)
delete mode 100644 src/backend/core/services/__init__.py
delete mode 100644 src/backend/core/services/agent_market_categories.py
delete mode 100644 src/backend/core/services/agent_market_service.py
delete mode 100644 src/backend/core/services/api_protocol_backfill.py
delete mode 100644 src/backend/core/services/artifact_edition.py
delete mode 100644 src/backend/core/services/automation_tool_routing.py
delete mode 100644 src/backend/core/services/chat_service.py
delete mode 100644 src/backend/core/services/commands/__init__.py
delete mode 100644 src/backend/core/services/desktop_cloud_bridge.py
delete mode 100644 src/backend/core/services/edition_system_config.py
delete mode 100644 src/backend/core/services/edition_workspace.py
delete mode 100644 src/backend/core/services/kb_edition.py
delete mode 100644 src/backend/core/services/lark_service.py
delete mode 100644 src/backend/core/services/log_service.py
delete mode 100644 src/backend/core/services/marketplace_service.py
delete mode 100644 src/backend/core/services/mcp_marketplace_service.py
delete mode 100644 src/backend/core/services/mcp_oauth_service.py
delete mode 100644 src/backend/core/services/mcp_tool_metadata.py
delete mode 100644 src/backend/core/services/mcp_tool_schema_backfill.py
delete mode 100644 src/backend/core/services/prompt_version_service.py
delete mode 100644 src/backend/core/services/session_operations/admission.py
delete mode 100644 src/backend/core/services/skill_draft_service.py
delete mode 100644 src/backend/core/services/system_config.py
delete mode 100644 src/backend/core/services/tool_effect_ledger.py
delete mode 100644 src/backend/core/services/user_agent_base.py
delete mode 100644 src/backend/core/spaces/edition_access.py
delete mode 100644 src/backend/core/spaces/edition_revocation.py
delete mode 100644 src/backend/core/vision/__init__.py
create mode 100644 src/backend/features/__init__.py
create mode 100644 src/backend/features/automation/README.md
create mode 100644 src/backend/features/automation/__init__.py
create mode 100644 src/backend/features/automation/admission.py
create mode 100644 src/backend/features/automation/api/__init__.py
rename src/backend/{api/routes/v1 => features/automation/api}/automations.py (94%)
rename src/backend/{core/services => features/automation}/delivery_targets.py (75%)
rename src/backend/{core/services/automation_execution.py => features/automation/execution.py} (96%)
create mode 100644 src/backend/features/automation/extension.py
rename src/backend/{core/services/loop_service.py => features/automation/loops.py} (99%)
rename src/backend/{core/db/models/automation.py => features/automation/models.py} (64%)
rename src/backend/{core/services/automation_notifications.py => features/automation/notifications.py} (100%)
rename src/backend/{core/services/automation_remote_effect.py => features/automation/remote_effect.py} (97%)
create mode 100644 src/backend/features/automation/runs.py
create mode 100644 src/backend/features/automation/scheduling.py
create mode 100644 src/backend/features/automation/schema.py
rename src/backend/{core/services/automation_service.py => features/automation/service.py} (73%)
create mode 100644 src/backend/features/automation/tool_routing.py
create mode 100644 src/backend/features/desktop/README.md
create mode 100644 src/backend/features/desktop/__init__.py
create mode 100644 src/backend/features/desktop/api/__init__.py
create mode 100644 src/backend/features/desktop/api/capability_auth.py
create mode 100644 src/backend/features/desktop/api/capability_bridge.py
create mode 100644 src/backend/features/desktop/api/capability_dependencies.py
create mode 100644 src/backend/features/desktop/api/capability_manifests.py
create mode 100644 src/backend/features/desktop/api/capability_mcp_gateway.py
create mode 100644 src/backend/features/desktop/api/capability_model_gateway.py
create mode 100644 src/backend/features/desktop/api/capability_tools.py
create mode 100644 src/backend/features/desktop/api/capability_workcopies.py
rename src/backend/{api/routes/v1 => features/desktop/api}/desktop.py (99%)
rename src/backend/{api/routes/v1 => features/desktop/api}/desktop_capabilities.py (80%)
create mode 100644 src/backend/features/desktop/api/desktop_capability.py
create mode 100644 src/backend/features/desktop/api/desktop_capability_state.py
rename src/backend/{api/routes/v1 => features/desktop/api}/desktop_login.py (99%)
rename src/backend/{api/routes/v1 => features/desktop/api}/desktop_observability.py (90%)
rename src/backend/{api/routes/v1 => features/desktop/api}/device_link.py (95%)
rename src/backend/{api/routes/v1 => features/desktop/api}/local.py (82%)
rename src/backend/{api/routes => features/desktop/api}/local_file_actions.py (93%)
create mode 100644 src/backend/features/desktop/auth/__init__.py
rename src/backend/{core/auth/desktop_bridge.py => features/desktop/auth/bridge.py} (92%)
rename src/backend/{core/auth/desktop_login_store.py => features/desktop/auth/login_store.py} (100%)
rename src/backend/{core/auth/desktop_ticket_store.py => features/desktop/auth/ticket_store.py} (100%)
create mode 100644 src/backend/features/desktop/bridge/__init__.py
rename src/backend/{core/services/desktop_cloud_bundles.py => features/desktop/bridge/bundles.py} (88%)
create mode 100644 src/backend/features/desktop/bridge/client.py
create mode 100644 src/backend/features/desktop/bridge/client_state.py
create mode 100644 src/backend/features/desktop/bridge/gateway.py
create mode 100644 src/backend/features/desktop/bridge/identity.py
create mode 100644 src/backend/features/desktop/bridge/managed_connectors.py
rename src/backend/{core/services/desktop_model_credentials.py => features/desktop/bridge/model_credentials.py} (86%)
rename src/backend/{core/services/desktop_release_events.py => features/desktop/bridge/release_events.py} (100%)
rename src/backend/{core/services/desktop_resource_plugins.py => features/desktop/bridge/resource_plugins.py} (86%)
rename src/backend/{core/services/desktop_cloud_skills.py => features/desktop/bridge/skills.py} (86%)
create mode 100644 src/backend/features/desktop/bridge/status.py
create mode 100644 src/backend/features/desktop/capabilities/__init__.py
rename src/backend/{core => features/desktop}/capabilities/agents.py (93%)
rename src/backend/{core => features/desktop}/capabilities/change_merge.py (94%)
rename src/backend/{core => features/desktop}/capabilities/change_signal.py (100%)
rename src/backend/{core => features/desktop}/capabilities/change_sync.py (87%)
rename src/backend/{core => features/desktop}/capabilities/connectors.py (90%)
rename src/backend/{core => features/desktop}/capabilities/credentials.py (100%)
rename src/backend/{core => features/desktop}/capabilities/dependency.py (94%)
rename src/backend/{core => features/desktop}/capabilities/dependency_declarations.py (100%)
rename src/backend/{core => features/desktop}/capabilities/device_catalog.py (81%)
rename src/backend/{core => features/desktop}/capabilities/device_plugin_catalog.py (69%)
rename src/backend/{core => features/desktop}/capabilities/discovery.py (85%)
rename src/backend/{core => features/desktop}/capabilities/invocation.py (74%)
rename src/backend/{core => features/desktop}/capabilities/local_plugin_runtime.py (72%)
rename src/backend/{core => features/desktop}/capabilities/local_projection.py (79%)
rename src/backend/{core => features/desktop}/capabilities/management_context.py (82%)
rename src/backend/{core => features/desktop}/capabilities/manifest_order.py (96%)
rename src/backend/{core => features/desktop}/capabilities/mcp_json.py (98%)
rename src/backend/{core => features/desktop}/capabilities/migration.py (92%)
rename src/backend/{core => features/desktop}/capabilities/paths.py (80%)
rename src/backend/{core => features/desktop}/capabilities/plugins.py (87%)
rename src/backend/{core => features/desktop}/capabilities/preparation.py (89%)
rename src/backend/{core => features/desktop}/capabilities/readiness.py (92%)
rename src/backend/{core => features/desktop}/capabilities/registry.py (84%)
create mode 100644 src/backend/features/desktop/capabilities/registry_types.py
rename src/backend/{core => features/desktop}/capabilities/resolver.py (98%)
create mode 100644 src/backend/features/desktop/capabilities/runtime/__init__.py
rename src/backend/{core => features/desktop}/capabilities/runtime/connectors.py (92%)
rename src/backend/{core => features/desktop}/capabilities/runtime/dependencies.py (93%)
rename src/backend/{core => features/desktop}/capabilities/runtime/preparation.py (86%)
rename src/backend/{core => features/desktop}/capabilities/runtime/state.py (89%)
rename src/backend/{core => features/desktop}/capabilities/session_availability.py (100%)
create mode 100644 src/backend/features/desktop/capabilities/skill_account.py
create mode 100644 src/backend/features/desktop/capabilities/skill_candidates.py
create mode 100644 src/backend/features/desktop/capabilities/skill_hashes.py
create mode 100644 src/backend/features/desktop/capabilities/skill_paths.py
create mode 100644 src/backend/features/desktop/capabilities/skill_publication.py
create mode 100644 src/backend/features/desktop/capabilities/skill_views.py
create mode 100644 src/backend/features/desktop/capabilities/skills.py
create mode 100644 src/backend/features/desktop/capabilities/skills_state.py
rename src/backend/{core => features/desktop}/capabilities/store.py (94%)
rename src/backend/{core => features/desktop}/capabilities/view.py (97%)
rename src/backend/{core => features/desktop}/capabilities/warmup.py (72%)
create mode 100644 src/backend/features/desktop/capability/__init__.py
rename src/backend/{core/services/desktop_capability_configs.py => features/desktop/capability/configs.py} (91%)
rename src/backend/{core/services/desktop_capability_credentials.py => features/desktop/capability/credentials.py} (98%)
rename src/backend/{core/services/desktop_capability_entities.py => features/desktop/capability/entities.py} (91%)
rename src/backend/{core/services/desktop_capability_mcp.py => features/desktop/capability/mcp.py} (85%)
rename src/backend/{core/services/desktop_capability_models.py => features/desktop/capability/models.py} (93%)
create mode 100644 src/backend/features/desktop/capability/revision_provider.py
rename src/backend/{core/services/desktop_capability_security.py => features/desktop/capability/security.py} (96%)
rename src/backend/{core/services/desktop_capability.py => features/desktop/capability/service.py} (90%)
rename src/backend/{core/services/desktop_capability_skills.py => features/desktop/capability/skills.py} (86%)
rename src/backend/{core/services/desktop_capability_sync_check.py => features/desktop/capability/sync_check.py} (79%)
rename src/backend/{core/services/capability_workcopies.py => features/desktop/capability/workcopies.py} (89%)
create mode 100644 src/backend/features/desktop/edition.py
create mode 100644 src/backend/features/desktop/extension.py
create mode 100644 src/backend/features/desktop/gateway/__init__.py
rename src/backend/{core/services/desktop_gateway_observability.py => features/desktop/gateway/observability.py} (98%)
create mode 100644 src/backend/features/desktop/local/__init__.py
rename src/backend/{core/services/local_file_actions.py => features/desktop/local/file_actions.py} (99%)
rename src/backend/{core/services/local_grant_service.py => features/desktop/local/grants.py} (86%)
rename src/backend/{core/services/local_management_migration.py => features/desktop/local/management_migration.py} (85%)
create mode 100644 src/backend/features/desktop/local/manager_execution.py
rename src/backend/{core/services/local_market_install.py => features/desktop/local/market_install.py} (61%)
rename src/backend/{core/services => features/desktop/local}/native_app_icons.py (100%)
rename src/backend/{core/plugins/local => features/desktop/local/plugins}/__init__.py (100%)
rename src/backend/{core/plugins/local => features/desktop/local/plugins}/assets.py (92%)
rename src/backend/{core/plugins/local => features/desktop/local/plugins}/legacy_migration.py (87%)
rename src/backend/{core/plugins/local => features/desktop/local/plugins}/service.py (85%)
create mode 100644 src/backend/features/desktop/local/plugins/sidecars.py
create mode 100644 src/backend/features/desktop/local/publication_provider.py
create mode 100644 src/backend/features/desktop/local/skills/__init__.py
rename src/backend/{core/services/local_skill_editor.py => features/desktop/local/skills/editor.py} (86%)
rename src/backend/{core/services/local_skill_service.py => features/desktop/local/skills/service.py} (81%)
rename src/backend/{core/services/local_skill_upload.py => features/desktop/local/skills/upload.py} (74%)
rename src/backend/{core/services/local_snapshot_service.py => features/desktop/local/snapshots.py} (100%)
rename src/backend/{core/db/models/capability.py => features/desktop/models.py} (60%)
create mode 100644 src/backend/features/desktop/observability/__init__.py
rename src/backend/{core/services/desktop_observation_context.py => features/desktop/observability/context.py} (78%)
rename src/backend/{core/services/desktop_observability_delivery.py => features/desktop/observability/delivery.py} (86%)
rename src/backend/{core/services/observability_projection.py => features/desktop/observability/fields.py} (96%)
rename src/backend/{core/services/desktop_observability.py => features/desktop/observability/projection.py} (98%)
rename src/backend/{core/services/desktop_observability_sync.py => features/desktop/observability/sync.py} (96%)
rename src/backend/{core/plugins/runtime/desktop_resolution.py => features/desktop/plugin_resolution.py} (85%)
create mode 100644 src/backend/features/desktop/run_capabilities/__init__.py
rename src/backend/{core/llm/factory/selection/device_preparation.py => features/desktop/run_capabilities/preparation.py} (83%)
create mode 100644 src/backend/features/desktop/run_capabilities/provider.py
rename src/backend/{core/agent_skills/backends/capability_store.py => features/desktop/skill_backend.py} (86%)
create mode 100644 src/backend/features/evaluation/README.md
create mode 100644 src/backend/features/evaluation/__init__.py
rename src/backend/{core/services/evaluation_admission.py => features/evaluation/admission.py} (93%)
create mode 100644 src/backend/features/evaluation/api/__init__.py
rename src/backend/{api/routes/v1 => features/evaluation/api}/evaluation_sandboxes.py (98%)
create mode 100644 src/backend/features/evaluation/extension.py
create mode 100644 src/backend/features/evaluation/models.py
create mode 100644 src/backend/features/evaluation/policy.py
create mode 100644 src/backend/features/evaluation/runtime/__init__.py
rename src/backend/{core/llm/evaluation_paths.py => features/evaluation/runtime/paths.py} (100%)
rename src/backend/{core/llm/evaluation_process_tools.py => features/evaluation/runtime/process_tools.py} (87%)
rename src/backend/{core/llm/evaluation_runtime.py => features/evaluation/runtime/runtime.py} (100%)
rename src/backend/{core/llm/evaluation_tools.py => features/evaluation/runtime/tools.py} (57%)
rename src/backend/{core/sandbox/providers/evaluation => features/evaluation/sandbox_provider}/__init__.py (100%)
rename src/backend/{core/sandbox/providers/evaluation => features/evaluation/sandbox_provider}/binding.py (100%)
rename src/backend/{core/sandbox/providers/evaluation => features/evaluation/sandbox_provider}/process_guard.py (100%)
rename src/backend/{core/sandbox/providers/evaluation => features/evaluation/sandbox_provider}/provider.py (98%)
rename src/backend/{core/services/evaluation_sandbox_service.py => features/evaluation/sandboxes.py} (90%)
create mode 100644 src/backend/features/hosted_apps/README.md
create mode 100644 src/backend/features/hosted_apps/__init__.py
create mode 100644 src/backend/features/hosted_apps/api/__init__.py
rename src/backend/{api/routes/v1 => features/hosted_apps/api}/application_transports.py (68%)
rename src/backend/{api/routes/v1 => features/hosted_apps/api}/applications.py (76%)
rename src/backend/{core/services/application_collections.py => features/hosted_apps/collections.py} (92%)
rename src/backend/{core/config/application_hosting.py => features/hosted_apps/config.py} (87%)
rename src/backend/{core/services/application_data.py => features/hosted_apps/data.py} (95%)
rename src/backend/{core/services/application_deployments.py => features/hosted_apps/deployments.py} (92%)
rename src/backend/{core/services/application_export.py => features/hosted_apps/export.py} (89%)
create mode 100644 src/backend/features/hosted_apps/extension.py
rename src/backend/{core/services/application_history.py => features/hosted_apps/history.py} (91%)
rename src/backend/{core/services/application_hosting_setup.py => features/hosted_apps/hosting_setup.py} (96%)
rename src/backend/{core/services/application_industry.py => features/hosted_apps/industry.py} (95%)
rename src/backend/{core/services/application_lifecycle.py => features/hosted_apps/lifecycle.py} (88%)
rename src/backend/{core/services/application_mcp.py => features/hosted_apps/mcp.py} (82%)
create mode 100644 src/backend/features/hosted_apps/models.py
rename src/backend/{core/services/application_operations.py => features/hosted_apps/operations.py} (84%)
rename src/backend/{core/services/application_projects.py => features/hosted_apps/projects.py} (95%)
rename src/backend/{core/services/application_publication.py => features/hosted_apps/publication.py} (92%)
rename src/backend/{core/services/application_relational.py => features/hosted_apps/relational.py} (98%)
create mode 100644 src/backend/features/hosted_apps/resources/__init__.py
create mode 100644 src/backend/features/hosted_apps/resources/admission.py
create mode 100644 src/backend/features/hosted_apps/resources/cleanup.py
rename src/backend/{core/services/application_schema.py => features/hosted_apps/schema.py} (100%)
rename src/backend/{core/services/application_sql_policy.py => features/hosted_apps/sql_policy.py} (96%)
rename src/backend/{core/services/application_store.py => features/hosted_apps/store.py} (94%)
create mode 100644 src/backend/features/installed.py
create mode 100644 src/backend/features/knowledge/README.md
rename src/backend/{core/kb => features/knowledge}/__init__.py (64%)
create mode 100644 src/backend/features/knowledge/api/__init__.py
create mode 100644 src/backend/features/knowledge/api/kb.py
create mode 100644 src/backend/features/knowledge/api/kb_assets.py
create mode 100644 src/backend/features/knowledge/api/kb_chunks.py
create mode 100644 src/backend/features/knowledge/api/kb_documents.py
create mode 100644 src/backend/features/knowledge/api/kb_helpers.py
create mode 100644 src/backend/features/knowledge/api/kb_mutations.py
create mode 100644 src/backend/features/knowledge/api/kb_spaces.py
create mode 100644 src/backend/features/knowledge/api/kb_state.py
rename src/backend/{api/routes/v1 => features/knowledge/api}/kb_wiki.py (93%)
create mode 100644 src/backend/features/knowledge/asset_captions.py
create mode 100644 src/backend/features/knowledge/asset_indexing.py
create mode 100644 src/backend/features/knowledge/catalog_options.py
rename src/backend/{core/kb/external_provider.py => features/knowledge/disabled_provider.py} (85%)
rename src/backend/{core/kb/external_retrieval.py => features/knowledge/disabled_retrieval.py} (100%)
rename src/backend/{core/kb => features/knowledge}/document_status.py (100%)
create mode 100644 src/backend/features/knowledge/edition.py
create mode 100644 src/backend/features/knowledge/enrichment.py
create mode 100644 src/backend/features/knowledge/extension.py
rename src/backend/{core/kb => features/knowledge}/index_queue.py (99%)
rename src/backend/{core/kb => features/knowledge}/index_worker.py (97%)
create mode 100644 src/backend/features/knowledge/kb_assets.py
create mode 100644 src/backend/features/knowledge/kb_assets_state.py
create mode 100644 src/backend/features/knowledge/kb_parser.py
create mode 100644 src/backend/features/knowledge/kb_parser_state.py
create mode 100644 src/backend/features/knowledge/kb_vector.py
create mode 100644 src/backend/features/knowledge/kb_vector_state.py
create mode 100644 src/backend/features/knowledge/models.py
create mode 100644 src/backend/features/knowledge/parser_chunks.py
create mode 100644 src/backend/features/knowledge/parser_extract.py
create mode 100644 src/backend/features/knowledge/parser_semantic.py
create mode 100644 src/backend/features/knowledge/parser_tokens.py
create mode 100644 src/backend/features/knowledge/parser_types.py
rename src/backend/{core/auth/kb_permissions.py => features/knowledge/personal_permissions.py} (97%)
rename src/backend/{api/routes/v1/kb_models.py => features/knowledge/personal_request_models.py} (100%)
rename src/backend/{core/content/kb_processing.py => features/knowledge/processing.py} (67%)
rename src/backend/{core/db/repository/kb.py => features/knowledge/repository.py} (97%)
rename src/backend/{core/services/kb_service.py => features/knowledge/service.py} (95%)
create mode 100644 src/backend/features/knowledge/vector_client.py
create mode 100644 src/backend/features/knowledge/vector_fields.py
create mode 100644 src/backend/features/knowledge/vector_indexing.py
rename src/backend/{core/kb => features/knowledge}/wiki/__init__.py (92%)
rename src/backend/{core/kb => features/knowledge}/wiki/cite.py (95%)
rename src/backend/{core/kb => features/knowledge}/wiki/config.py (99%)
rename src/backend/{core/kb => features/knowledge}/wiki/dedup.py (94%)
rename src/backend/{core/kb => features/knowledge}/wiki/extract.py (93%)
rename src/backend/{core/kb => features/knowledge}/wiki/finalize.py (92%)
rename src/backend/{core/kb => features/knowledge}/wiki/jobs.py (99%)
rename src/backend/{core/kb => features/knowledge}/wiki/linkify.py (100%)
rename src/backend/{core/kb => features/knowledge}/wiki/llm.py (96%)
create mode 100644 src/backend/features/knowledge/wiki/local_graph.py
create mode 100644 src/backend/features/knowledge/wiki/local_projection.py
create mode 100644 src/backend/features/knowledge/wiki/local_provider.py
create mode 100644 src/backend/features/knowledge/wiki/local_provider_state.py
rename src/backend/{core/kb => features/knowledge}/wiki/pipeline.py (95%)
rename src/backend/{core/kb => features/knowledge}/wiki/prompts.py (100%)
rename src/backend/{core/kb => features/knowledge}/wiki/reduce.py (93%)
rename src/backend/{core/kb => features/knowledge}/wiki/slug.py (100%)
rename src/backend/{core/kb => features/knowledge}/wiki/store.py (98%)
rename src/backend/{core/kb => features/knowledge}/wiki/taxonomy.py (89%)
rename src/backend/{core/kb => features/knowledge}/wiki/worker.py (94%)
rename src/backend/{core/kb => features/knowledge}/wiki_router.py (88%)
create mode 100644 src/backend/features/marketplace/README.md
create mode 100644 src/backend/features/marketplace/__init__.py
create mode 100644 src/backend/features/marketplace/agent_catalog.py
create mode 100644 src/backend/features/marketplace/agent_categories.py
create mode 100644 src/backend/features/marketplace/agent_installation.py
create mode 100644 src/backend/features/marketplace/agent_metadata.py
create mode 100644 src/backend/features/marketplace/agent_publishing.py
create mode 100644 src/backend/features/marketplace/agent_submission_data.py
create mode 100644 src/backend/features/marketplace/agent_submissions.py
rename src/backend/{core/services/marketplace_agent_versions.py => features/marketplace/agent_versions.py} (88%)
create mode 100644 src/backend/features/marketplace/agents_state.py
create mode 100644 src/backend/features/marketplace/api/__init__.py
rename src/backend/{api/routes/v1 => features/marketplace/api}/agent_marketplace.py (82%)
rename src/backend/{api/routes/v1 => features/marketplace/api}/catalog_ownership.py (94%)
rename src/backend/{api/routes/v1 => features/marketplace/api}/marketplace.py (84%)
rename src/backend/{api/routes/v1 => features/marketplace/api}/mcp_marketplace.py (79%)
create mode 100644 src/backend/features/marketplace/core_catalog.py
create mode 100644 src/backend/features/marketplace/edition.py
create mode 100644 src/backend/features/marketplace/extension.py
create mode 100644 src/backend/features/marketplace/mcp_administration.py
rename src/backend/{core/config/mcp_marketplace_catalog.py => features/marketplace/mcp_catalog.py} (100%)
create mode 100644 src/backend/features/marketplace/mcp_credentials.py
create mode 100644 src/backend/features/marketplace/mcp_curated.py
create mode 100644 src/backend/features/marketplace/mcp_installation.py
create mode 100644 src/backend/features/marketplace/mcp_listing.py
rename src/backend/{core/services/mcp_marketplace_monitor.py => features/marketplace/mcp_monitor.py} (55%)
create mode 100644 src/backend/features/marketplace/mcp_publishing.py
create mode 100644 src/backend/features/marketplace/mcp_revalidation.py
create mode 100644 src/backend/features/marketplace/mcp_state.py
create mode 100644 src/backend/features/marketplace/mcp_submissions.py
create mode 100644 src/backend/features/marketplace/mcp_validation.py
create mode 100644 src/backend/features/marketplace/mcp_versions.py
create mode 100644 src/backend/features/marketplace/models.py
create mode 100644 src/backend/features/marketplace/package_source.py
rename src/backend/{core/services/marketplace_listing.py => features/marketplace/personal_listing.py} (97%)
rename src/backend/{core/auth/marketplace_visibility.py => features/marketplace/personal_visibility.py} (100%)
rename src/backend/{core/plugins/management/market.py => features/marketplace/plugin_market.py} (73%)
create mode 100644 src/backend/features/marketplace/skill_archives.py
create mode 100644 src/backend/features/marketplace/skill_catalog.py
create mode 100644 src/backend/features/marketplace/skill_installation.py
create mode 100644 src/backend/features/marketplace/skill_metadata.py
create mode 100644 src/backend/features/marketplace/skill_publishing.py
create mode 100644 src/backend/features/marketplace/skill_submission_data.py
create mode 100644 src/backend/features/marketplace/skill_submissions.py
rename src/backend/{core/services/marketplace_skill_versions.py => features/marketplace/skill_versions.py} (72%)
create mode 100644 src/backend/features/marketplace/skills_state.py
create mode 100644 src/backend/features/marketplace/versions/__init__.py
rename src/backend/{core/services/marketplace_version_archive.py => features/marketplace/versions/archive.py} (100%)
rename src/backend/{core/services/marketplace_version_config.py => features/marketplace/versions/config.py} (95%)
rename src/backend/{core/services/marketplace_version_sources.py => features/marketplace/versions/sources.py} (84%)
rename src/backend/{core/services/marketplace_version_store.py => features/marketplace/versions/store.py} (90%)
create mode 100644 src/backend/features/sites/README.md
create mode 100644 src/backend/features/sites/__init__.py
create mode 100644 src/backend/features/sites/api/__init__.py
rename src/backend/{api/routes/v1 => features/sites/api}/internal_site_auth.py (100%)
rename src/backend/{api/routes/v1 => features/sites/api}/internal_sites.py (94%)
rename src/backend/{api/routes/v1 => features/sites/api}/local_site_sources.py (94%)
rename src/backend/{api/routes => features/sites/api}/site_gate.py (100%)
rename src/backend/{api/routes/v1 => features/sites/api}/sites.py (95%)
rename src/backend/{api/routes => features/sites/api}/sites_serve.py (82%)
rename src/backend/{core/plugins/local/site_upgrade.py => features/sites/builtin_upgrade.py} (83%)
rename src/backend/{core/services/desktop_site_publish.py => features/sites/desktop_publish.py} (83%)
create mode 100644 src/backend/features/sites/edition.py
create mode 100644 src/backend/features/sites/extension.py
rename src/backend/{core/services/site_listing.py => features/sites/listing.py} (94%)
rename src/backend/{core/services/local_site_sources.py => features/sites/local_sources.py} (93%)
rename src/backend/{core/services/site_management.py => features/sites/management.py} (95%)
rename src/backend/{core/db/models/site.py => features/sites/models.py} (97%)
rename src/backend/{core/services/site_packaging.py => features/sites/packaging.py} (92%)
rename src/backend/{core/services/site_password.py => features/sites/password.py} (100%)
rename src/backend/{core/services/site_access_policy.py => features/sites/personal_access.py} (97%)
rename src/backend/{core/db/models/site_scope.py => features/sites/personal_scope.py} (100%)
rename src/backend/{core/services/site_rate_limit.py => features/sites/rate_limit.py} (100%)
rename src/backend/{core/db/repository/site.py => features/sites/repository.py} (98%)
rename src/backend/{core/services/site_service.py => features/sites/service.py} (97%)
create mode 100644 src/backend/features/sites/tools/__init__.py
rename src/backend/{core/llm/tools/design_picker_tool.py => features/sites/tools/design_picker.py} (86%)
create mode 100644 src/backend/features/workbench/README.md
rename src/backend/{core/applications => features/workbench}/__init__.py (100%)
rename src/backend/{core/applications => features/workbench}/activity.py (98%)
create mode 100644 src/backend/features/workbench/api/__init__.py
rename src/backend/{api/routes/v1 => features/workbench/api}/plugin_ui.py (91%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench.py (82%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_activity.py (82%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_application_config.py (88%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_auth.py (92%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_capabilities.py (81%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_diagnostics.py (97%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_events.py (85%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_preferences.py (97%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_resources.py (69%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_revision_leases.py (97%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_state.py (98%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_upgrades.py (98%)
rename src/backend/{api/routes/v1 => features/workbench/api}/workbench_workspace.py (83%)
rename src/backend/{core/applications => features/workbench}/application_config.py (93%)
rename src/backend/{core/applications => features/workbench}/application_config_bindings.py (82%)
rename src/backend/{core/applications => features/workbench}/background.py (63%)
rename src/backend/{core/applications => features/workbench}/capabilities.py (95%)
rename src/backend/{core/applications => features/workbench}/catalog.py (96%)
rename src/backend/{core/applications => features/workbench}/coordination.py (100%)
rename src/backend/{core/applications => features/workbench}/diagnostic_probe.py (98%)
rename src/backend/{core/applications => features/workbench}/diagnostic_resources.py (100%)
rename src/backend/{core/applications => features/workbench}/diagnostics.py (99%)
rename src/backend/{core/applications => features/workbench}/directory_revision.py (96%)
create mode 100644 src/backend/features/workbench/edition.py
create mode 100644 src/backend/features/workbench/extension.py
rename src/backend/{core/applications => features/workbench}/external.py (94%)
rename src/backend/{core/applications => features/workbench}/external_auth.py (99%)
rename src/backend/{core/applications => features/workbench}/external_boundary.py (97%)
rename src/backend/{core/applications => features/workbench}/external_embed.py (97%)
rename src/backend/{core/applications => features/workbench}/installation_binding.py (77%)
rename src/backend/{core/applications => features/workbench}/local_plugin_upgrades.py (86%)
rename src/backend/{core/applications => features/workbench}/migration.py (97%)
rename src/backend/{core/applications => features/workbench}/migration_compat.py (88%)
rename src/backend/{core/applications => features/workbench}/migration_plan.py (96%)
rename src/backend/{core/applications => features/workbench}/migration_snapshot.py (91%)
create mode 100644 src/backend/features/workbench/models.py
rename src/backend/{core/applications => features/workbench}/origin.py (100%)
create mode 100644 src/backend/features/workbench/package_source.py
rename src/backend/{core/applications => features/workbench}/plugin_candidate_storage.py (88%)
rename src/backend/{core/applications => features/workbench}/plugin_catalog.py (95%)
rename src/backend/{core/applications => features/workbench}/plugin_revisions.py (89%)
rename src/backend/{core/applications => features/workbench}/plugin_upgrades.py (90%)
rename src/backend/{core/applications => features/workbench}/policy.py (94%)
rename src/backend/{core/applications => features/workbench}/preferences.py (96%)
rename src/backend/{core/applications => features/workbench}/protocol.py (100%)
create mode 100644 src/backend/features/workbench/resource_deadlines.py
create mode 100644 src/backend/features/workbench/resource_owner.py
rename src/backend/{core/applications => features/workbench}/revocation.py (87%)
rename src/backend/{core/applications => features/workbench}/revocation_sweep.py (78%)
rename src/backend/{core/applications => features/workbench}/schemas/application.schema.json (100%)
rename src/backend/{core/applications => features/workbench}/schemas/host.json (100%)
rename src/backend/{core/applications => features/workbench}/schemas/module.schema.json (100%)
rename src/backend/{core/applications => features/workbench}/state.py (96%)
rename src/backend/{core/applications => features/workbench}/state_recovery.py (100%)
rename src/backend/{core/plugins => features/workbench}/ui/__init__.py (100%)
rename src/backend/{core/plugins => features/workbench}/ui/contract.py (99%)
rename src/backend/{core/plugins => features/workbench}/ui/contributions.py (87%)
rename src/backend/{core/plugins => features/workbench}/ui/data_proxy.py (97%)
rename src/backend/{core/plugins => features/workbench}/ui/declarations.py (99%)
rename src/backend/{core/plugins => features/workbench}/ui/primitives.py (99%)
rename src/backend/{core/plugins => features/workbench}/ui/workspace_enabled.py (100%)
rename src/backend/{core/applications => features/workbench}/workspace_page.py (98%)
create mode 100644 src/backend/hugagent_cli/schema.py
create mode 100644 src/backend/integrations/__init__.py
create mode 100644 src/backend/integrations/api/__init__.py
rename src/backend/{api/routes/v1 => integrations/api}/integrations.py (86%)
create mode 100644 src/backend/integrations/channels/README.md
rename src/backend/{core => integrations}/channels/__init__.py (75%)
rename src/backend/{core => integrations}/channels/adapters/__init__.py (100%)
create mode 100644 src/backend/integrations/channels/adapters/dingtalk.py
create mode 100644 src/backend/integrations/channels/adapters/dingtalk_delivery.py
create mode 100644 src/backend/integrations/channels/adapters/dingtalk_history.py
create mode 100644 src/backend/integrations/channels/adapters/dingtalk_settings.py
create mode 100644 src/backend/integrations/channels/adapters/lark.py
create mode 100644 src/backend/integrations/channels/adapters/lark_delivery.py
create mode 100644 src/backend/integrations/channels/adapters/lark_messages.py
create mode 100644 src/backend/integrations/channels/adapters/lark_settings.py
rename src/backend/{core => integrations}/channels/adapters/wecom.py (99%)
rename src/backend/{core => integrations}/channels/adapters/weixin.py (99%)
create mode 100644 src/backend/integrations/channels/api/__init__.py
rename src/backend/{api/routes/v1 => integrations/channels/api}/channel_desktop.py (75%)
rename src/backend/{api/routes/v1 => integrations/channels/api}/channels.py (89%)
rename src/backend/{core/services/channel_desktop_worker.py => integrations/channels/desktop_worker.py} (81%)
create mode 100644 src/backend/integrations/channels/extension.py
create mode 100644 src/backend/integrations/channels/inbound.py
create mode 100644 src/backend/integrations/channels/inbound_attachments.py
create mode 100644 src/backend/integrations/channels/inbound_delivery.py
create mode 100644 src/backend/integrations/channels/inbound_history.py
create mode 100644 src/backend/integrations/channels/inbound_observation.py
create mode 100644 src/backend/integrations/channels/inbound_sessions.py
create mode 100644 src/backend/integrations/channels/inbound_state.py
rename src/backend/{core => integrations}/channels/manager.py (96%)
rename src/backend/{core => integrations}/channels/markdown.py (100%)
create mode 100644 src/backend/integrations/channels/models.py
rename src/backend/{core => integrations}/channels/outbound.py (70%)
rename src/backend/{core => integrations}/channels/protocol.py (100%)
rename src/backend/{core => integrations}/channels/registry.py (88%)
rename src/backend/{core/services/channel_relay.py => integrations/channels/relay.py} (98%)
rename src/backend/{core/services/channel_relay_dispatch.py => integrations/channels/relay_dispatch.py} (54%)
rename src/backend/{core/services/channel_relay_rpc.py => integrations/channels/relay_rpc.py} (63%)
rename src/backend/{core/db/repository/channel.py => integrations/channels/repository.py} (98%)
rename src/backend/{core/services/channel_service.py => integrations/channels/service.py} (70%)
create mode 100644 src/backend/integrations/channels/tools/__init__.py
rename src/backend/{core/llm/tools/channel_attachment_tool.py => integrations/channels/tools/attachment.py} (52%)
create mode 100644 src/backend/integrations/dingtalk/README.md
create mode 100644 src/backend/integrations/dingtalk/__init__.py
create mode 100644 src/backend/integrations/dingtalk/cube_credentials.py
create mode 100644 src/backend/integrations/dingtalk/environment.py
create mode 100644 src/backend/integrations/dingtalk/extension.py
create mode 100644 src/backend/integrations/dingtalk/models.py
create mode 100644 src/backend/integrations/dingtalk/mounts.py
create mode 100644 src/backend/integrations/dingtalk/parsing.py
create mode 100644 src/backend/integrations/dingtalk/repository.py
create mode 100644 src/backend/integrations/dingtalk/sandbox.py
rename src/backend/{core/services/dingtalk_service.py => integrations/dingtalk/service.py} (75%)
create mode 100644 src/backend/integrations/dingtalk/storage.py
create mode 100644 src/backend/integrations/email/README.md
create mode 100644 src/backend/integrations/email/__init__.py
create mode 100644 src/backend/integrations/email/cube_credentials.py
create mode 100644 src/backend/integrations/email/extension.py
create mode 100644 src/backend/integrations/email/models.py
create mode 100644 src/backend/integrations/email/mounts.py
create mode 100644 src/backend/integrations/email/repository.py
create mode 100644 src/backend/integrations/email/sandbox.py
rename src/backend/{core/services/email_service.py => integrations/email/service.py} (91%)
create mode 100644 src/backend/integrations/email/storage.py
create mode 100644 src/backend/integrations/lark/README.md
create mode 100644 src/backend/integrations/lark/__init__.py
create mode 100644 src/backend/integrations/lark/configuration.py
create mode 100644 src/backend/integrations/lark/cube_credentials.py
create mode 100644 src/backend/integrations/lark/extension.py
create mode 100644 src/backend/integrations/lark/login.py
create mode 100644 src/backend/integrations/lark/models.py
create mode 100644 src/backend/integrations/lark/mounts.py
create mode 100644 src/backend/integrations/lark/parsing.py
create mode 100644 src/backend/integrations/lark/process.py
create mode 100644 src/backend/integrations/lark/repository.py
create mode 100644 src/backend/integrations/lark/sandbox.py
create mode 100644 src/backend/integrations/lark/service.py
create mode 100644 src/backend/integrations/lark/state.py
create mode 100644 src/backend/integrations/lark/storage.py
create mode 100644 src/backend/integrations/yida/README.md
create mode 100644 src/backend/integrations/yida/__init__.py
create mode 100644 src/backend/integrations/yida/cube_credentials.py
rename src/backend/{core/services/yida_execution.py => integrations/yida/execution.py} (97%)
create mode 100644 src/backend/integrations/yida/extension.py
create mode 100644 src/backend/integrations/yida/models.py
create mode 100644 src/backend/integrations/yida/mounts.py
create mode 100644 src/backend/integrations/yida/sandbox.py
rename src/backend/{core/services/yida_service.py => integrations/yida/service.py} (99%)
create mode 100644 src/backend/integrations/yida/skill_content.py
create mode 100644 src/backend/integrations/yida/storage.py
create mode 100644 src/backend/orchestration/automations/__init__.py
rename src/backend/orchestration/{schedulers => automations}/automation_artifacts.py (97%)
create mode 100644 src/backend/orchestration/automations/automation_conversation.py
create mode 100644 src/backend/orchestration/automations/automation_delivery.py
create mode 100644 src/backend/orchestration/automations/automation_dispatch.py
create mode 100644 src/backend/orchestration/automations/automation_lease.py
create mode 100644 src/backend/orchestration/automations/automation_recovery.py
create mode 100644 src/backend/orchestration/automations/automation_scheduler.py
delete mode 100644 src/backend/orchestration/chat_run_executor.py
delete mode 100644 src/backend/orchestration/citation_anchor.py
delete mode 100644 src/backend/orchestration/commands/__init__.py
delete mode 100644 src/backend/orchestration/commands/runtime.py
create mode 100644 src/backend/orchestration/configuration.py
delete mode 100644 src/backend/orchestration/local_plugin_sidecars.py
create mode 100644 src/backend/orchestration/loops/__init__.py
create mode 100644 src/backend/orchestration/loops/admission.py
rename src/backend/orchestration/{autonomous_loop.py => loops/driver.py} (98%)
rename src/backend/orchestration/{loop_planner.py => loops/planner.py} (97%)
create mode 100644 src/backend/orchestration/loops/presentation.py
create mode 100644 src/backend/orchestration/loops/recovery.py
rename src/backend/orchestration/{loop_evaluator.py => loops/requirements.py} (83%)
rename src/backend/orchestration/{subagents/loop_reviewer.py => loops/reviewer.py} (78%)
create mode 100644 src/backend/orchestration/loops/run.py
delete mode 100644 src/backend/orchestration/memory_integration.py
create mode 100644 src/backend/orchestration/pending.py
create mode 100644 src/backend/orchestration/plans/__init__.py
create mode 100644 src/backend/orchestration/plans/admission.py
create mode 100644 src/backend/orchestration/plans/execute_run.py
create mode 100644 src/backend/orchestration/plans/generate_run.py
rename src/backend/orchestration/{subagents/plan_mode.py => plans/mode.py} (89%)
delete mode 100644 src/backend/orchestration/schedulers/automation_conversation.py
delete mode 100644 src/backend/orchestration/schedulers/automation_dispatch.py
delete mode 100644 src/backend/orchestration/schedulers/automation_lease.py
delete mode 100644 src/backend/orchestration/schedulers/automation_recovery.py
delete mode 100644 src/backend/orchestration/schedulers/automation_scheduler.py
delete mode 100644 src/backend/orchestration/streaming.py
delete mode 100644 src/backend/orchestration/subagents/__init__.py
delete mode 100644 src/backend/orchestration/subagents/ontology_reviewer.py
rename src/backend/orchestration/{schedulers => workers}/__init__.py (100%)
rename src/backend/orchestration/{schedulers => workers}/_worker_base.py (96%)
rename src/backend/{core/services => orchestration/workers}/edition_startup.py (100%)
rename src/backend/orchestration/{schedulers => workers}/evolution_scheduler.py (98%)
rename src/backend/orchestration/{schedulers => workers}/memory_ttl_scheduler.py (95%)
rename src/backend/orchestration/{schedulers => workers}/persona_distill_worker.py (94%)
create mode 100644 src/backend/orchestration/workers/run_recovery.py
delete mode 100644 src/backend/orchestration/workflow.py
create mode 100644 src/backend/personal_agent/device_link/README.md
rename src/backend/{core => personal_agent}/device_link/__init__.py (84%)
rename src/backend/{core => personal_agent}/device_link/driver.py (100%)
rename src/backend/{core => personal_agent}/device_link/local_api.py (100%)
rename src/backend/{core => personal_agent}/device_link/runner.py (98%)
rename src/backend/{core => personal_agent}/device_link/store.py (100%)
delete mode 100644 src/backend/scripts/_loop_convergence_unit.py
delete mode 100644 src/backend/scripts/_loop_db_resume_unit.py
delete mode 100644 src/backend/scripts/_loop_evaluator_route_unit.py
delete mode 100644 src/backend/scripts/_loop_integration_test.py
delete mode 100644 src/backend/scripts/_loop_llmeval_smoke.py
delete mode 100644 src/backend/scripts/_loop_preflight.py
delete mode 100644 src/backend/scripts/_loop_selfverify_smoke.py
delete mode 100644 src/backend/scripts/_loop_smoke.py
delete mode 100644 src/backend/scripts/_loop_test_edgebench.py
delete mode 100644 src/backend/scripts/desktop_cloud_wrapper.py
delete mode 100644 src/backend/scripts/desktop_fixture_mcp.py
delete mode 100644 src/backend/scripts/dual_mode_e2e.py
delete mode 100644 src/backend/scripts/edgebench/ann_judge.py
delete mode 100644 src/backend/scripts/edgebench/perm_opt_judge.py
delete mode 100644 src/backend/scripts/edit_chat_message.py
delete mode 100644 src/backend/scripts/evaluation_sandbox_e2e.py
delete mode 100644 src/backend/scripts/export_content.py
delete mode 100755 src/backend/scripts/harness_fault_injection.py
delete mode 100644 src/backend/scripts/import_cherry_agents.py
delete mode 100644 src/backend/scripts/import_content.py
delete mode 100644 src/backend/scripts/run_evolution_benchmark.py
delete mode 100644 src/backend/scripts/run_evolution_llm_ab.py
delete mode 100644 src/backend/scripts/workbench_migration.py
create mode 100644 src/backend/tests/api/test_loop_cancel_scope.py
create mode 100644 src/backend/tests/api/test_project_local_resolve.py
create mode 100644 src/backend/tests/auth/test_auth_dependencies_off_loop.py
create mode 100644 src/backend/tests/auth/test_bearer_identity.py
delete mode 100644 src/backend/tests/capabilities/test_credential_continuity.py
create mode 100644 src/backend/tests/chat/test_manual_compaction_api.py
create mode 100644 src/backend/tests/chat/test_mcp_result_payload.py
create mode 100644 src/backend/tests/chat/test_message_length_limits.py
create mode 100644 src/backend/tests/chat/test_quoted_follow_up.py
create mode 100644 src/backend/tests/chat/test_snapshot_transaction_cleanup.py
create mode 100644 src/backend/tests/content/test_artifact_default_path.py
create mode 100644 src/backend/tests/core/runs/test_automation_journal_guard.py
create mode 100644 src/backend/tests/db_support.py
create mode 100644 src/backend/tests/features/__init__.py
create mode 100644 src/backend/tests/features/automation/__init__.py
create mode 100644 src/backend/tests/features/automation/run_fixtures.py
create mode 100644 src/backend/tests/features/automation/test_automation_delivery_deadline.py
rename src/backend/tests/{ => features/automation}/test_automation_execution_location.py (80%)
create mode 100644 src/backend/tests/features/automation/test_automation_lease_close.py
create mode 100644 src/backend/tests/features/automation/test_automation_manual_trigger.py
rename src/backend/tests/{services => features/automation}/test_automation_notifications.py (98%)
create mode 100644 src/backend/tests/features/automation/test_automation_oneshot_finalize.py
create mode 100644 src/backend/tests/features/automation/test_automation_ownership_postgres.py
rename src/backend/tests/{ => features/automation}/test_automation_remote_effect.py (74%)
create mode 100644 src/backend/tests/features/automation/test_automation_run_ownership.py
create mode 100644 src/backend/tests/features/automation/test_delivery_target_migration.py
create mode 100644 src/backend/tests/features/automation/test_durable_admission.py
rename src/backend/tests/{services => features/automation}/test_loop_service_harness.py (98%)
create mode 100644 src/backend/tests/features/automation/test_queue_postgres.py
create mode 100644 src/backend/tests/features/desktop/__init__.py
create mode 100644 src/backend/tests/features/desktop/capabilities/__init__.py
rename src/backend/tests/{ => features/desktop}/capabilities/_cloud_identity.py (100%)
rename src/backend/tests/{ => features/desktop}/capabilities/conftest.py (65%)
rename src/backend/tests/{ => features/desktop}/capabilities/management_browser_server.py (88%)
rename src/backend/tests/{ => features/desktop}/capabilities/runtime_recovery_support.py (75%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_account_authorization.py (52%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_agent_rerun_identity.py (75%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_agent_runtime_grants.py (62%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_agents_plugins.py (90%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_autonomous_loop_scopes.py (73%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_available_runtime.py (82%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_capability_change_sync.py (81%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_catalog_cloud_only_in_hybrid.py (72%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_catalog_plugin_components_hidden.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_change_merge.py (90%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_change_signal.py (97%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_identity.py (96%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_management.py (88%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_parallel_preparation.py (68%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_plugin_binding_runtime.py (76%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_sticky_plugins.py (83%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_cloud_workcopies.py (91%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_core_layers.py (96%)
create mode 100644 src/backend/tests/features/desktop/capabilities/test_credential_continuity.py
rename src/backend/tests/{ => features/desktop}/capabilities/test_dependency_closure.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_capabilities_api.py (85%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_content_safety.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_declaration_export.py (89%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_model_reasoning_safety.py (88%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_resource_plugins.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_token_security.py (90%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_desktop_warmup.py (61%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_device_api_contract.py (74%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_device_catalog_projection.py (91%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_dynamic_model_epoch_cache.py (80%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_explicit_desktop_invocation.py (79%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_gateway_invocation_proof.py (78%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_gateway_unknown_outcome.py (61%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_hybrid_owned_plugins.py (50%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_initial_sync_readiness.py (51%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_junction_windows_paths.py (86%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_loader_store_mode.py (74%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_local_folder_discovery.py (90%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_local_management_migration.py (83%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_local_skill_projection.py (83%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_management_readiness.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_management_tools.py (56%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_manager_lifecycle.py (91%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_manager_regressions.py (85%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_manifest_order.py (85%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_manifest_tool_schema.py (76%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_mcp_json.py (98%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_mcp_json_consistency.py (99%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_memory_epoch_cache.py (79%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_migration.py (89%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_migration_preservation.py (96%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_model_credential_reference.py (86%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_model_error_diagnostics.py (83%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_owned_connector_assembly.py (55%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_partial_session_availability.py (73%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_plugin_connections.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_plugin_device_assets.py (89%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_prepare_lock_not_held_over_io.py (98%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_progressive_plugins.py (72%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_frozen_bindings.py (63%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_publication_recovery.py (70%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_recovery.py (58%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_scope_consumers.py (87%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_scopes.py (81%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_selection_recovery.py (66%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_runtime_session_recovery.py (68%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_selection_dependency_sources.py (79%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_selection_readiness.py (80%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_store_file_integrity.py (88%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_toolkit_runtime_alias.py (83%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_user_facing_errors.py (81%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_view_os_errors.py (95%)
rename src/backend/tests/{ => features/desktop}/capabilities/test_workbench_local_upgrades.py (84%)
rename src/backend/tests/{ => features/desktop}/desktop_login_browser_server.py (91%)
create mode 100644 src/backend/tests/features/desktop/index_fixtures.py
create mode 100644 src/backend/tests/features/desktop/observability_cases_1.py
create mode 100644 src/backend/tests/features/desktop/observability_cases_2.py
create mode 100644 src/backend/tests/features/desktop/observability_support.py
rename src/backend/tests/{ => features/desktop}/run_desktop_browser_e2e.py (99%)
rename src/backend/tests/{ => features/desktop}/test_desktop_auth.py (96%)
rename src/backend/tests/{misc => features/desktop}/test_desktop_bridge.py (90%)
rename src/backend/tests/{misc => features/desktop}/test_desktop_capability.py (81%)
rename src/backend/tests/{misc => features/desktop}/test_desktop_capability_models.py (91%)
rename src/backend/tests/{misc => features/desktop}/test_desktop_cloud_skills.py (84%)
rename src/backend/tests/{ => features/desktop}/test_desktop_device_login.py (97%)
rename src/backend/tests/{ => features/desktop}/test_desktop_memory_persistence.py (96%)
rename src/backend/tests/{api => features/desktop}/test_desktop_model_gateway.py (88%)
create mode 100644 src/backend/tests/features/desktop/test_desktop_observability.py
rename src/backend/tests/{ => features/desktop}/test_desktop_observability_authenticated_delivery.py (77%)
rename src/backend/tests/{ => features/desktop}/test_desktop_observability_delivery.py (75%)
rename src/backend/tests/{api => features/desktop}/test_desktop_release_events.py (96%)
rename src/backend/tests/{ => features/desktop}/test_desktop_sync_locking.py (74%)
rename src/backend/tests/{api => features/desktop}/test_desktop_updates.py (98%)
rename src/backend/tests/{api => features/desktop}/test_local_file_actions.py (89%)
rename src/backend/tests/{services => features/desktop}/test_local_grant_service.py (98%)
rename src/backend/tests/{services => features/desktop}/test_local_snapshot_service.py (96%)
create mode 100644 src/backend/tests/features/evaluation/__init__.py
create mode 100644 src/backend/tests/features/evaluation/conftest.py
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_admission.py (97%)
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_binding.py (98%)
rename src/backend/tests/{llm => features/evaluation}/test_evaluation_factory.py (87%)
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_process_guard.py (97%)
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_provider.py (96%)
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_router.py (78%)
rename src/backend/tests/{llm => features/evaluation}/test_evaluation_runtime.py (88%)
rename src/backend/tests/{sandbox => features/evaluation}/test_evaluation_service.py (93%)
create mode 100644 src/backend/tests/features/hosted_apps/__init__.py
rename src/backend/tests/{api => features/hosted_apps}/application_hosting_support.py (75%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_agent_contract.py (76%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_collections.py (87%)
rename src/backend/tests/{services => features/hosted_apps}/test_application_desktop.py (80%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_export.py (91%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_hosting.py (92%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_personal_mcp.py (91%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_project_tools.py (87%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_projects.py (94%)
rename src/backend/tests/{api => features/hosted_apps}/test_application_retirement.py (94%)
rename src/backend/tests/{api => features/hosted_apps}/test_hosting_remediation.py (82%)
create mode 100644 src/backend/tests/features/hosted_apps/test_schema_lifecycle.py
create mode 100644 src/backend/tests/features/knowledge/__init__.py
create mode 100644 src/backend/tests/features/knowledge/kb_multimodal_assets_cases_1.py
create mode 100644 src/backend/tests/features/knowledge/kb_multimodal_assets_cases_2.py
create mode 100644 src/backend/tests/features/knowledge/kb_multimodal_assets_support.py
create mode 100644 src/backend/tests/features/knowledge/kb_wiki_end_to_end_cases_1.py
create mode 100644 src/backend/tests/features/knowledge/kb_wiki_end_to_end_cases_2.py
create mode 100644 src/backend/tests/features/knowledge/kb_wiki_end_to_end_support.py
create mode 100644 src/backend/tests/features/knowledge/test_host_contributions.py
rename src/backend/tests/{mcp => features/knowledge}/test_kb_backend_routing.py (74%)
rename src/backend/tests/{ => features/knowledge}/test_kb_document_status.py (89%)
rename src/backend/tests/{services => features/knowledge}/test_kb_index_queue.py (98%)
rename src/backend/tests/{services => features/knowledge}/test_kb_index_worker.py (88%)
create mode 100644 src/backend/tests/features/knowledge/test_kb_multimodal_assets.py
rename src/backend/tests/{mcp => features/knowledge}/test_kb_vector_safety.py (57%)
rename src/backend/tests/{mcp => features/knowledge}/test_kb_vector_timeouts.py (68%)
create mode 100644 src/backend/tests/features/knowledge/test_kb_wiki_end_to_end.py
rename src/backend/tests/{services => features/knowledge}/test_kb_wiki_pipeline.py (96%)
create mode 100644 src/backend/tests/features/marketplace/__init__.py
create mode 100644 src/backend/tests/features/marketplace/mcp_marketplace_cases_1.py
create mode 100644 src/backend/tests/features/marketplace/mcp_marketplace_cases_2.py
create mode 100644 src/backend/tests/features/marketplace/mcp_marketplace_cases_3.py
create mode 100644 src/backend/tests/features/marketplace/mcp_marketplace_cases_4.py
create mode 100644 src/backend/tests/features/marketplace/mcp_marketplace_support.py
rename src/backend/tests/{ => features/marketplace}/test_agent_marketplace.py (69%)
rename src/backend/tests/{skills => features/marketplace}/test_marketplace_service.py (73%)
rename src/backend/tests/{skills => features/marketplace}/test_marketplace_submission.py (54%)
create mode 100644 src/backend/tests/features/marketplace/test_mcp_marketplace.py
create mode 100644 src/backend/tests/features/marketplace/test_oauth_sdk2_contract.py
create mode 100644 src/backend/tests/features/sites/__init__.py
create mode 100644 src/backend/tests/features/sites/conftest.py
rename src/backend/tests/{capabilities => features/sites}/test_cloud_site_publish.py (82%)
rename src/backend/tests/{llm => features/sites}/test_design_pick.py (99%)
rename src/backend/tests/{llm => features/sites}/test_desktop_site_paths.py (88%)
rename src/backend/tests/{capabilities => features/sites}/test_local_project_site_publish.py (93%)
rename src/backend/tests/{capabilities => features/sites}/test_local_site_sources.py (88%)
rename src/backend/tests/{api => features/sites}/test_site_access_password.py (93%)
rename src/backend/tests/{api => features/sites}/test_site_contention_postgres.py (92%)
rename src/backend/tests/{capabilities => features/sites}/test_site_listing.py (90%)
rename src/backend/tests/{capabilities => features/sites}/test_site_plugin_upgrade.py (85%)
rename src/backend/tests/{services => features/sites}/test_site_rate_limit.py (97%)
create mode 100644 src/backend/tests/features/test_execution_identity_contract.py
create mode 100644 src/backend/tests/features/test_host_runtime.py
create mode 100644 src/backend/tests/features/test_installed_modules.py
create mode 100644 src/backend/tests/features/test_run_capability_host.py
create mode 100644 src/backend/tests/features/workbench/__init__.py
rename src/backend/tests/{ => features/workbench}/test_plugin_ui_contract.py (97%)
rename src/backend/tests/{ => features/workbench}/test_plugin_ui_routes.py (96%)
rename src/backend/tests/{ => features/workbench}/test_workbench_activity_routes.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_actual_process_observer.py (93%)
rename src/backend/tests/{ => features/workbench}/test_workbench_application_config_http.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_approval_lease_http.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_auth_routes.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_background_subpage_http.py (96%)
rename src/backend/tests/{ => features/workbench}/test_workbench_candidate_storage.py (91%)
rename src/backend/tests/{ => features/workbench}/test_workbench_capability_routes.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_closing_isolation_http.py (94%)
rename src/backend/tests/{ => features/workbench}/test_workbench_cookie_boundaries.py (94%)
rename src/backend/tests/{ => features/workbench}/test_workbench_coordination.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_diagnostic_failure_http.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_diagnostics.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_embed_routes.py (96%)
rename src/backend/tests/{ => features/workbench}/test_workbench_external_routes.py (98%)
rename src/backend/tests/{ => features/workbench}/test_workbench_invalidation.py (94%)
rename src/backend/tests/{ => features/workbench}/test_workbench_migration.py (98%)
rename src/backend/tests/{ => features/workbench}/test_workbench_migration_auth_gate.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_observer_paths.py (93%)
rename src/backend/tests/{ => features/workbench}/test_workbench_official_capabilities_http.py (93%)
rename src/backend/tests/{ => features/workbench}/test_workbench_origins.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_plugin_routes.py (97%)
rename src/backend/tests/{ => features/workbench}/test_workbench_preferences_routes.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_protocol.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_provider_scope_http.py (93%)
rename src/backend/tests/{ => features/workbench}/test_workbench_resource_owner_migrations.py (99%)
rename src/backend/tests/{ => features/workbench}/test_workbench_resources.py (85%)
rename src/backend/tests/{ => features/workbench}/test_workbench_revocation_failure_http.py (90%)
rename src/backend/tests/{ => features/workbench}/test_workbench_routing_routes.py (94%)
rename src/backend/tests/{ => features/workbench}/test_workbench_state_binding.py (94%)
rename src/backend/tests/{ => features/workbench}/test_workbench_state_concurrency.py (87%)
rename src/backend/tests/{ => features/workbench}/test_workbench_state_routes.py (95%)
rename src/backend/tests/{ => features/workbench}/test_workbench_upgrade_routes.py (97%)
rename src/backend/tests/{ => features/workbench}/workbench_capabilities_http_fixture.py (96%)
rename src/backend/tests/{ => features/workbench}/workbench_diagnostic_http_fixture.py (95%)
rename src/backend/tests/{ => features/workbench}/workbench_embed_http_fixture.py (95%)
rename src/backend/tests/{ => features/workbench}/workbench_http_fixture.py (95%)
rename src/backend/tests/{ => features/workbench}/workbench_observed_runner_fixture.py (90%)
rename src/backend/tests/{ => features/workbench}/workbench_observer_paths.py (98%)
rename src/backend/tests/{ => features/workbench}/workbench_overflow_http_fixture.py (95%)
rename src/backend/tests/{ => features/workbench}/workbench_plugin_http_fixture.py (96%)
rename src/backend/tests/{ => features/workbench}/workbench_plugin_support.py (100%)
rename src/backend/tests/{ => features/workbench}/workbench_resource_observer.py (92%)
rename src/backend/tests/{ => features/workbench}/workbench_routing_http_fixture.py (88%)
create mode 100644 src/backend/tests/infra/test_byte_range.py
create mode 100644 src/backend/tests/infra/test_db_off_loop_call_sites.py
create mode 100644 src/backend/tests/infra/test_db_thread_budget.py
create mode 100644 src/backend/tests/infra/test_no_session_on_event_loop.py
create mode 100644 src/backend/tests/integrations/__init__.py
create mode 100644 src/backend/tests/integrations/channels/__init__.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_1.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_2.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_3.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_4.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_5.py
create mode 100644 src/backend/tests/integrations/channels/bot_cases_6.py
create mode 100644 src/backend/tests/integrations/channels/bot_support.py
create mode 100644 src/backend/tests/integrations/channels/test_adapter_method_binding.py
create mode 100644 src/backend/tests/integrations/channels/test_channel_bots.py
rename src/backend/tests/{ => integrations/channels}/test_channel_bots_api.py (93%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_e2e.py (84%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_http.py (84%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_migration.py (97%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_relay.py (88%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_relay_api.py (83%)
rename src/backend/tests/{ => integrations/channels}/test_channel_desktop_worker.py (75%)
rename src/backend/tests/{ => integrations/channels}/test_channel_receiver_lifecycle.py (94%)
rename src/backend/tests/{ => integrations/channels}/test_weixin_connection_health.py (95%)
rename src/backend/tests/{ => integrations/channels}/test_weixin_qr_protocol.py (95%)
rename src/backend/tests/{ => integrations/dingtalk}/test_dingtalk_integration.py (91%)
rename src/backend/tests/{ => integrations/email}/test_email_integration.py (87%)
create mode 100644 src/backend/tests/integrations/lark/test_app_lifecycle.py
rename src/backend/tests/{ => integrations/lark}/test_lark_auth_status.py (79%)
rename src/backend/tests/{ => integrations/yida}/test_yida_integration.py (94%)
create mode 100644 src/backend/tests/llm/test_delta_coalescer.py
create mode 100644 src/backend/tests/llm/test_mcp_factory_e2e.py
create mode 100644 src/backend/tests/llm/test_mcp_human_wait_timeout.py
create mode 100644 src/backend/tests/llm/test_mcp_native_adversarial.py
create mode 100644 src/backend/tests/llm/test_mcp_native_capabilities.py
delete mode 100644 src/backend/tests/llm/test_mcp_pool_cache.py
create mode 100644 src/backend/tests/llm/test_mcp_runtime_pool.py
create mode 100644 src/backend/tests/llm/test_mcp_session_ownership.py
create mode 100644 src/backend/tests/llm/test_migration_vendor_datasource.py
create mode 100644 src/backend/tests/llm/test_stream_wrapper_cleanup.py
create mode 100644 src/backend/tests/orchestration/automation_test_support.py
create mode 100644 src/backend/tests/orchestration/hook_support.py
create mode 100644 src/backend/tests/orchestration/test_agent_runtime_resources.py
delete mode 100644 src/backend/tests/orchestration/test_harness_process_restart.py
create mode 100644 src/backend/tests/orchestration/test_observation_writer.py
create mode 100644 src/backend/tests/orchestration/test_pending_workflows.py
create mode 100644 src/backend/tests/orchestration/test_plan_early_cancel.py
create mode 100644 src/backend/tests/orchestration/test_runtime_phase_lifecycle.py
create mode 100644 src/backend/tests/orchestration/test_tool_effect_ledger_2.py
create mode 100644 src/backend/tests/orchestration/test_tool_effect_ledger_3.py
create mode 100644 src/backend/tests/orchestration/test_tool_effect_ledger_4.py
create mode 100644 src/backend/tests/orchestration/test_tool_effect_ledger_5.py
create mode 100644 src/backend/tests/orchestration/tool_effect_support.py
rename src/backend/tests/{ => personal_agent}/test_device_link.py (97%)
create mode 100644 src/backend/tests/sandbox/test_controller_capacity.py
create mode 100644 src/backend/tests/sandbox/test_runner_user_env.py
create mode 100644 src/backend/tests/sandbox/test_runtime_ownership.py
create mode 100644 src/backend/tests/sandbox/test_sandbox_init.py
delete mode 100644 src/backend/tests/services/test_kb_multimodal_assets.py
delete mode 100644 src/backend/tests/services/test_kb_wiki_end_to_end.py
create mode 100644 src/backend/tests/services/test_local_instruction_hierarchy.py
create mode 100644 src/backend/tests/test_agent_version_history.py
delete mode 100644 src/backend/tests/test_automation_manual_trigger.py
delete mode 100644 src/backend/tests/test_automation_oneshot_finalize.py
delete mode 100644 src/backend/tests/test_channel_bots.py
create mode 100644 src/backend/tests/test_cli_cold_bootstrap.py
create mode 100644 src/backend/tests/test_cli_engine.py
create mode 100644 src/backend/tests/test_credential_registry.py
create mode 100644 src/backend/tests/test_db_import_order.py
delete mode 100644 src/backend/tests/test_desktop_observability.py
create mode 100644 src/backend/tests/test_hook_contributions.py
create mode 100644 src/backend/tests/test_local_mcp_http_readiness.py
create mode 100644 src/backend/tests/test_local_onboarding.py
create mode 100644 src/backend/tests/test_mcp_canonical_url.py
delete mode 100644 src/backend/tests/test_mcp_marketplace.py
create mode 100644 src/backend/tests/test_package_sources.py
create mode 100644 src/backend/tests/test_personal_agent_prompt_cache.py
create mode 100644 src/backend/tests/test_route_import_order.py
create mode 100644 src/backend/tests/test_session_database_isolation.py
create mode 100644 src/backend/tests/test_tool_contributions.py
create mode 100644 src/backend/tests/test_user_cleanup.py
create mode 100644 src/frontend/scripts/test-canvas-quote-browser.mjs
create mode 100644 src/frontend/scripts/test-canvas-viewers-browser.mjs
create mode 100644 src/frontend/scripts/test-canvas-zoom-browser.mjs
create mode 100644 src/frontend/scripts/test-mcp-tool-output.ts
delete mode 100644 src/frontend/src/components/canvas/CanvasFileRenderers.tsx
create mode 100644 src/frontend/src/components/canvas/CanvasZoomControl.tsx
create mode 100644 src/frontend/src/components/canvas/canvasDocument.ts
delete mode 100644 src/frontend/src/components/canvas/canvasFileCategory.ts
create mode 100644 src/frontend/src/components/canvas/canvasZoom.ts
create mode 100644 src/frontend/src/components/canvas/find/CanvasFind.css
create mode 100644 src/frontend/src/components/canvas/find/CanvasFind.tsx
create mode 100644 src/frontend/src/components/canvas/find/domFindSource.ts
create mode 100644 src/frontend/src/components/canvas/find/index.ts
create mode 100644 src/frontend/src/components/canvas/find/textSearch.ts
create mode 100644 src/frontend/src/components/canvas/outline/CanvasOutline.css
create mode 100644 src/frontend/src/components/canvas/outline/CanvasOutline.tsx
create mode 100644 src/frontend/src/components/canvas/outline/domOutline.ts
create mode 100644 src/frontend/src/components/canvas/outline/index.ts
create mode 100644 src/frontend/src/components/canvas/renderers/DocxRenderer.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/FallbackRenderers.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/HtmlRenderer.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/ImageRenderer.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/PdfRenderer.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/PdfViewer.css
create mode 100644 src/frontend/src/components/canvas/renderers/PdfViewer.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/TextRenderers.css
create mode 100644 src/frontend/src/components/canvas/renderers/TextRenderers.tsx
create mode 100644 src/frontend/src/components/canvas/renderers/index.ts
create mode 100644 src/frontend/src/components/canvas/renderers/pdfjs.ts
create mode 100644 src/frontend/src/components/canvas/renderers/previewError.ts
create mode 100644 src/frontend/src/components/canvas/selection/SpreadsheetQuoteButton.css
create mode 100644 src/frontend/src/components/canvas/selection/SpreadsheetQuoteButton.tsx
create mode 100644 src/frontend/src/components/canvas/selection/canvasChat.ts
create mode 100644 src/frontend/src/components/canvas/selection/canvasLocator.ts
create mode 100644 src/frontend/src/components/canvas/selection/useCanvasSelection.tsx
create mode 100644 src/frontend/src/components/chat/AgentCursor.tsx
create mode 100644 src/frontend/src/components/common/AgentMark.tsx
create mode 100644 src/frontend/src/components/common/RollingText.tsx
create mode 100644 src/frontend/src/components/common/selection/SelectionToolbar.css
create mode 100644 src/frontend/src/components/common/selection/useSelectionToolbar.tsx
create mode 100644 src/frontend/src/components/file/MediaPlayer.css
create mode 100644 src/frontend/src/components/file/MediaPlayer.tsx
delete mode 100644 src/frontend/src/components/tool/PendingStepRow.tsx
create mode 100644 src/frontend/src/components/tool/ToolKindStack.tsx
create mode 100644 src/frontend/src/components/tool/toolIcons.tsx
create mode 100644 src/frontend/src/hooks/useActivityAge.ts
create mode 100644 src/frontend/src/styles/chat/agent-activity.css
create mode 100644 src/frontend/src/utils/elapsed.ts
create mode 100644 src/frontend/src/utils/filePreviewKind.ts
create mode 100644 src/frontend/src/utils/mcpResult.ts
create mode 100644 src/frontend/src/utils/quotedFollowUp.ts
create mode 100644 src/frontend/src/utils/toolIconKind.ts
create mode 100644 src/frontend/src/utils/turnActivity.ts
diff --git a/.claude/skills/hugagent-backend-dev/references/api-envelope.md b/.claude/skills/hugagent-backend-dev/references/api-envelope.md
index 841012ce..a0d9525c 100644
--- a/.claude/skills/hugagent-backend-dev/references/api-envelope.md
+++ b/.claude/skills/hugagent-backend-dev/references/api-envelope.md
@@ -78,4 +78,8 @@ return error_response(code=20001, message="Bad request", status_code=400)
1. **路由中只用 `success_response` / `created_response` / `paginated_response`**
2. **错误通过抛 `AppException` 子类,由全局 handler 转 `error_response`**
3. **trace_id 默认自动生成,无需手动传**
-4. **SSE 端点(流式聊天)不使用信封,直接发 SSE 事件**
+4. **SSE 端点不使用信封**:用 `sse_response(generator, extra_headers=...)` 返回(自带 `Cache-Control: no-cache`、
+ `X-Accel-Buffering: no` 等 `SSE_HEADERS`);聊天回合的 SSE 输出统一走 `api/transports/run_sse.py`,不要自写一套
+5. **License 402** 只来自 `edition_ee/licensing/features.py` 的 `FeatureNotLicensed`(40201)/ `SeatLimitExceeded`(40202),
+ 路由与服务层不要手搓 402
+6. 时间字段序列化用 `.isoformat()`(模型存的是带时区的 UTC),不要输出无时区字符串
diff --git a/.claude/skills/hugagent-backend-dev/references/architecture.md b/.claude/skills/hugagent-backend-dev/references/architecture.md
index 34c6a766..306f4283 100644
--- a/.claude/skills/hugagent-backend-dev/references/architecture.md
+++ b/.claude/skills/hugagent-backend-dev/references/architecture.md
@@ -1,129 +1,164 @@
# 后端架构参考
-## 请求流转
+## 1. 顶层布局与依赖方向
```
-Browser → Nginx (:3000 /api/ proxy) → FastAPI (api/app.py)
- → api/middleware/ (CORS → Logging → Error Handler → license_gate)
- → api/routes/v1/*.py (路由层;CE 注册表 api/routes/v1/__init__.py,EE 注册表 edition_ee/routes/registry.py)
- → core/services/*.py (服务层 — 业务逻辑)
- → core/db/repository/ (仓库层 — 数据访问,按领域分文件)
- → core/db/models/ (ORM 包 → PostgreSQL/SQLite)
+src/backend/
+├── api/ HTTP 协议层:app.py(组装)、deps.py、middleware/、routes/v1/(CE_ROUTERS)、transports/run_sse.py、startup/
+├── core/ 内核:领域逻辑与可复用运行时(不导入 features / integrations / personal_agent / orchestration)
+├── orchestration/ 模式编排:plans/、loops/、automations/、workers/(周期任务与恢复)
+├── features/ 可安装的产品功能模块(各自 README + extension.py + models.py + api/)
+├── integrations/ 外部系统集成:channels(IM 机器人框架)、dingtalk、lark、email、yida、api
+├── personal_agent/ Personal Agent 模块(同样经 extension.py 接入)
+├── edition_ee/ 商业版专属:路由注册表、License、Team/RBAC、EE 表、KB provider、EE 服务(CE 树物理删除)
+├── extensions/ 随包资源:skills/、agents/、plugins/(default + marketplace)、mcp_servers/;根路径唯一来源 paths.py
+├── prompts/ 提示词装配运行时 + prompt_text/ 文件兜底
+├── hugagent_cli/ 命令行客户端
+├── alembic/ 迁移(EE 主链;CE 用独立 ce_0001 基线)
+└── tests/ pytest(按领域子目录:api/ chat/ core/ features// infra/ llm/ orchestration/ …)
```
-## 分层职责
+```
+api ──► orchestration ──► core
+ │ │ ▲
+ └──► features / integrations / personal_agent ──┘ (通过 core.harness.registry 注册钩子接入)
+edition_ee ──► core / features (CE 派生树中不存在)
+```
+
+- **core 永远不导入功能模块的实现**。功能模块在 `extension.setup()` 里调用 `core.harness.registry.register_*` 把能力挂进来;
+ 关闭时什么也不注册,core 的行为等同于未安装。
+- **不保留转发垫片**:模块搬家时直接改所有调用方的 import,不留旧路径 re-export(ADR 20261021)。
+- 单个模块 ≤ 500 行(存量超限文件已登记为例外,不要新增)。
+
+## 2. core/ 子包职责
+
+| 子包 | 职责 |
+|---|---|
+| `agent_runtime/` | 一次普通智能体回合:`runner.astream_chat_workflow` 选主智能体 / 直连路径;`prepare_*`(建 agent、组装输入)、`stream_*`(模型与工具事件 → 既有事件协议)、`*_state`(单轮有类型状态)、`integrations/`(记忆、压缩、本体证据/修复、演化) |
+| `runs/` | 持久化执行所有权:租约 / 心跳、取消、事件日志与续播(`events/`)、看门狗、工具副作用账本(`tool_effects/`)、恢复 |
+| `chat/` | 会话与消息读写、模式(modes)、分叉、引用、追问、压缩;`runtime/`(聊天 worker:消费回合事件、在 runs 租约下持久化输出、排队 / 插话 / 暂停 / 恢复);`operations/`、`projection/` |
+| `llm/` | AgentScope 适配与智能体装配:`factory/`(build / assembly / tools / prompts)、`runtime/`(流式循环、钩子适配、工具执行、人机交互)、`citations/`(锚点发号)、`mcp/`、`subagents/`、`tools/`、`tool_permissions/`、提示词版本池 |
+| `models/` | 模型供应商与选择:chat_models、providers、failover、reasoning effort、embedding / reranking、vision、用量 |
+| `capabilities/` | 能力生命周期:`catalog/`、`skills/`、`plugins/`(含插件资源)、`mcp/`、`agents/`、manifest 协议 |
+| `harness/` | 宿主接缝:`registry/`(功能模块注册钩子的唯一入口)、执行清单、观测事件、日志写入、用量 |
+| `auth/` | `backend.py`(`get_current_user` / `UserContext`)、`bearer.py`(mock/remote Bearer 异步校验 + 共享缓存)、session、本地账号、API Key、能力位、权限接口 |
+| `db/` | `engine.py`(Base / SessionLocal / `get_db` / 线程预算)、`threads.py`(`run_db` / `run_in_session` / `off_loop`)、`models/`(核心 ORM 包)、`repository/`、`utc_datetime.py`、各类升级 / 修复脚本 |
+| `config/` | `settings.py`(`settings` 冻结 dataclass:deploy / auth / sso / db / llm / memory / storage / kb / redis / server / sandbox / edition / branding …)、`catalog.json`、`mcp_config.py`、`display_names.py`、`runtime_env.py` |
+| `infra/` | `responses.py`、`exceptions.py`、`logging.py`、`time.py`(`utc_now`)、`background.py`(`spawn`)、redis、metrics、限流 / 熔断、leader 选举、加密、脱敏 |
+| `files/` | 产物(artifacts)、内容解析(content)、空间文件(spaces)、存储协议(storage:local / s3 / oss) |
+| `projects/` | 项目、项目环境、指令、本地工作区、个人项目作用域 |
+| `memory/` | 分层记忆(L1 画像 / L2 向量 / L3 图谱)、抽取流水线、outbox、脱敏、TTL |
+| `ontology/` | 本体治理:schema、校验、运行时闸门、输出评审、修订 |
+| `evolution/` | 协同进化共享契约与证据(控制面在 EE) |
+| `jobs/` | 后台作业运行时(作业 SDK、监督、通知、文件) |
+| `sandbox/` | 沙箱协议 / 工厂 / providers(script_runner、opensandbox、cube[EE])、runner 进程服务、进程 / 会话 / 工作区 / 权限 / OS 隔离 |
+
+## 3. 功能模块(features / integrations / personal_agent)
+
+`features/installed.py::INSTALLED_FEATURES` 是全量安装清单(CE 由 `ce/overlay/src/backend/features/installed.py` 覆盖)。
+
+| 模块 | 内容 |
+|---|---|
+| `features/automation` | 定时任务:调度、执行、投递目标、通知、远程副作用 |
+| `features/knowledge` | 自建知识库:解析、分块、向量、资产索引、Wiki;外部 provider 接缝 |
+| `features/marketplace` | 技能 / 智能体 / MCP 市场:目录、安装、发布、版本、导出 |
+| `features/workbench` | 可扩展工作台:应用目录、外部应用 / 嵌入 / 外部认证、插件 UI、资源实例、偏好、诊断、迁移 |
+| `features/hosted_apps` | 托管业务应用(SQL / MCP):部署、数据、发布、资源 |
+| `features/sites` | 站点托管与发布(静态服务、访问密码、打包、本机发布) |
+| `features/desktop` | 桌面双端:能力桥、网关、本机能力、登录桥接、可观测性 |
+| `features/evaluation` | 评测沙箱(严格 OpenSandbox) |
+| `features/distillation` · `security_console` · `datasources` · `sandbox_ops` | EE:技能 / 人格蒸馏、安全台、数据源、沙箱运维 |
+| `integrations/channels` + `dingtalk` / `lark` / `email` / `yida` | 入站渠道机器人框架与各平台适配 |
+| `personal_agent` | Personal Agent:会话、渠道、调度、投递、设备连接、工作区人格 |
+
+模块约定:
```
-┌─────────────────────────────────────────┐
-│ Routes (api/routes/v1/) │ ← HTTP 入口、参数校验、响应包装
-│ - 依赖注入: auth, db │
-│ - Pydantic 请求模型 │
-│ - _to_dict() 转换函数 │
-│ - 调用 Service │
-├─────────────────────────────────────────┤
-│ Services (core/services/) │ ← 业务逻辑、权限校验、审计
-│ - 构造函数接收 db: Session │
-│ - 内部创建 Repository │
-│ - 幂等操作 (ensure_xxx) │
-│ - 抛 AppException │
-├─────────────────────────────────────────┤
-│ Repository (core/db/repository/) │ ← 数据访问抽象(包,10 个领域文件)
-│ - CRUD 操作 │
-│ - 分页查询 │
-│ - 软删除过滤 │
-│ - 不含业务逻辑 │
-├─────────────────────────────────────────┤
-│ Models (core/db/models/) │ ← ORM 定义(包,17 个领域文件)
-│ - SQLAlchemy declarative │
-│ - 索引、约束、关系 │
-│ - EE 专属表登记 edition_ee/db/edition_tables.py │
-│ - Alembic 迁移 │
-└─────────────────────────────────────────┘
+features//
+├── README.md 负责什么、公开入口、依赖方向、版本差异
+├── extension.py NAME + setup() -> bool;可选 register_tools / register_gateway_contributions /
+│ register_catalog_options / register_retrieval_context
+├── models.py ORM(按安装清单加载,与运行开关无关)
+├── edition.py (可选)CE/EE 差异唯一入口,CE overlay 只覆盖它
+├── service.py / repository.py / <领域>.py
+└── api/ 路由文件(多数进 CE_ROUTERS / EE_ROUTERS,可关闭的由 setup() 内 register_router 挂载)
```
-## 聊天流式请求流转
+## 4. 启动与路由装配(api/app.py)
+
+1. `orchestration.configuration.register_configuration()`、`features.register_run_capabilities()`、
+ `register_model_credentials()`、`integrations.register_credentials()`
+2. 挂 health、`api/routes` 文件下载路由
+3. `iter_edition_routers(CE_ROUTERS)` 全部 include;`iter_edition_routers(EE_ROUTERS)` include 并附加
+ `edition_router_dependencies(policy_key)`(License 能力位,未授权 402)
+4. `register_product_tools()` / `register_catalog_options()` / `register_package_sources()` / `register_resource_owners()`
+5. `load_extensions(INSTALLED_FEATURES)`:先 `load_models`,再逐个 `.extension.setup()`;然后挂
+ `core.harness.registry.routers()` 里登记的模块路由
+6. `sites_serve_router`(静态站点兜底)→ 登录路由 → mock SSO(按登录模式)→ 本地模式前端静态托管(最后)
+
+注册表规则:
+
+- `CE_ROUTERS`(`api/routes/v1/__init__.py`):二元组 `(module, attr)`;短名解析为 `api.routes.v1.`,
+ 含点号的写绝对路径(`"features.sites.api.sites"`)。**顺序即注册顺序**,同前缀族的先后有语义(例:`plugin_ui` 必须在
+ `plugins` 之前,否则 `/v1/plugins/{slug}` 会吞掉 `/v1/plugins/ui-contributions`)
+- `EE_ROUTERS`(`edition_ee/routes/registry.py`):三元组 `(module, attr, policy_key)`;`policy_key` 取
+ `edition_ee/licensing/features.py::Feature` 的值(`content_admin` / `audit` / `multi_tenancy` / `system_config` /
+ `billing` / `model_gateway` / `evolution_control` …);`None` 仅用于 License 失效时也必须可达的入口(登录、换证)
+- `api.edition.EE_ROUTERS` 是懒加载接缝;CE overlay 把它换成空表
+- **不要**在 `app.py` 手工 `include_router()`
+
+## 5. 一次聊天回合的流转
```
-POST /v1/agents/responses
- → api/routes/v1/chats/__init__.py
- → orchestration/chat_run_executor.py # ChatRun + Redis Stream(后台 run,SSE 跟随,断线续播)
- → orchestration/workflow.py # 流式编排主入口
- → core/services/chat_mode_service.py # 解析对话模式 ChatModeSpec(standard/turbo/市场模式;装配契约)
- → orchestration/memory_integration.py # 检索分层记忆并注入(user-role 冻结块,600ms 预算)
- → core/llm/factory/build.py # 构建 AgentScope 2.0 ReActAgent(按 ChatModeSpec 收窄工具面)
- → core/llm/mcp_manager.py # MCP 客户端池(streamable-http → mcp 容器)
- → core/config/mcp_config.py # MCP server 定义(端口真源 mcp_servers/_ports.py)
- → core/llm/middlewares.py # AS2 中间件(动态模型、文件上下文、CitationAnchorMiddleware、OntologyGateMiddleware)
- → prompts/prompt_runtime.py # 装配系统提示词(DB 版本池优先,prompt_text/ 兜底)
- → orchestration/streaming.py # StreamingAgent:把 AS2 事件流映射为内部块事件
- → orchestration/citation_anchor.py # 引用锚点唯一发号(AnchorAllocator,e1/e2/…,经中间件回注工具结果)
- → core/memory/service.py # 流结束后后台保存记忆(L1/L2/L3)
- → orchestration/followups.py # 流结束后后台生成追问建议(写入消息 extra_data)
+POST /v1/agents/responses api/routes/v1/agent_responses.py(+ chats/ 子路由)
+ → 准入与回合登记 core/chat/runtime/admission.py · core/runs(租约、事件日志)
+ → 聊天 worker core/chat/runtime/worker.py(消费事件、按顺序持久化输出)
+ → core/agent_runtime/runner.astream_chat_workflow
+ → prepare_main / prepare_direct 建 agent、组装输入(模式 core/chat/modes.py、记忆 integrations/memory.py)
+ → core/llm/factory/build.py AgentScope agent:模型选择、工具集、MCP(core/llm/mcp)、技能、提示词
+ → stream_main / stream_direct 模型与工具事件 → 事件协议(core/llm/runtime/streaming.py、stream_events.py)
+ → core/llm/citations/ 引用锚点发号(e1/e2…)
+ → 结束后:本体评审、记忆 / 演化结算、追问(core/chat/runtime/followups.py)
+ → SSE 输出 api/transports/run_sse.py(跟随事件日志;续播 GET /v1/chats/stream/{run_id}/subscription)
```
-> `orchestration/strategy.py` 仍在但已退化为占位符(两个分支都返回 MainOnlyStrategy),不再是主链路环节。
-
-**平级编排入口**(不走普通 chat run 的独立驱动):
-
-| 入口 | 文件 | 说明 |
-|------|------|------|
-| 自主循环 | `orchestration/autonomous_loop.py` + `loop_planner.py` / `loop_evaluator.py` + `subagents/loop_reviewer.py` | driver 持有需求台账 feature_list.json,逐条注入 + 只读 reviewer 验收;路由 `v1/loops.py` |
-| 批量执行 | `orchestration/batch_orchestrator.py` | batch_confirm 确认后逐行执行 |
-| 计划模式 | `orchestration/subagents/plan_mode.py` | plan_generate / plan_execute run |
-| 渠道入站 | `core/channels/`(protocol/registry/manager/inbound/outbound) | IM 消息归一为 InboundMsg 后复用聊天编排,回复经 markdown.py 降级回发 |
-| 定时任务 | `orchestration/schedulers/`(automation / distillation_cron / evolution / memory_ttl) | croniter 调度 |
-
-SSE 事件(前端解析真源 `src/frontend/src/hooks/chatStream.ts`):
-
-- 生命周期:`run_started`(首帧,携带续播 run_id)、`meta`、`error`、`end` / `data: [DONE]`(终止)、心跳为 SSE 注释行
-- 正文/思考:`content`(delta 增量;别名 ai_message/text/delta)、`content_replace`、`thinking`、`compaction_notice`、`steer_applied`
-- 工具:`tool_call_start`、`tool_call_delta`(参数流式)、`tool_call`、`tool_result`、`tool_pending`、`model_progress`(活性信号)
-- 交互确认:`batch_confirm`、`file_confirm`、`design_pick`
-- 计划/子智能体:`plan_update`、`subagent_event`
-- 本体治理:`ontology_activation` / `ontology_gate` / `ontology_review` / `ontology_repair` / `ontology_revision` / `ontology_revision_thinking`
-
-断线续播:`GET /v1/chats/stream/{run_id}`。追问建议(follow_up_questions)流结束后后台生成,也可能以 `follow_up` 事件流内直送。
-
-## 模块索引
-
-| 模块 | 路径 | 职责 |
-|------|------|------|
-| App 入口 | `api/app.py` | FastAPI 实例、中间件;路由按注册表自动注册 |
-| CE 路由注册表 | `api/routes/v1/__init__.py` | **CE_ROUTERS 单一真源**(二元组);re-export EE_ROUTERS |
-| EE 路由注册表 | `edition_ee/routes/registry.py` | **EE_ROUTERS 单一真源**(三元组,带 license 能力位) |
-| 依赖注入 | `api/deps.py` | require_admin(ADMIN_TOKEN)、require_config(CONFIG_TOKEN)、require_admin_or_config、require_system_settings、require_super_admin |
-| 用户认证 | `core/auth/backend.py` | get_current_user / UserContext(桌面桥接 → session → API-Key → mock/remote) |
-| 健康检查 | `api/health.py` | /health, /ready, /live |
-| Schema | `api/schemas.py` | 请求/响应 Pydantic 模型 |
-| 路由 | `api/routes/v1/` | 66 个路由文件 |
-| 技能引擎 | `core/agent_skills/` | SKILL.md 解析、多源加载、{dir} 沙箱路径注入 |
-| 生成物 | `core/artifacts/` | 注册与下载(store.py,local/oss 双模) |
-| 认证 | `core/auth/` | backend.py, session.py, sso.py, permissions_iface.py(CE/EE 接缝) |
-| 渠道 | `core/channels/` | 入站机器人框架:钉钉/飞书/企微/微信 adapter;加渠道 = 写 adapter + registry.py 注册 |
-| 聊天 | `core/chat/` | context.py, tool_log.py |
-| 配置 | `core/config/` | settings.py + catalog 五件套(catalog.json/catalog.py/loader/resolver/migration)+ mcp_config.py |
-| 内容 | `core/content/` | 内容块、file_parser.py |
-| 数据库 | `core/db/` | engine.py, models/(17 领域文件), repository/(10 领域文件), model_extensions.py, edition_tables.py(垫片) |
-| 进化 | `core/evolution/` | GCE 共享契约(Episode/候选/结算);控制面在 edition_ee/evolution |
-| 基础设施 | `core/infra/` | exceptions, responses, logging, metrics, 限流, Redis, crypto |
-| 知识库 | `core/kb/` | 分块、向量化、混合检索 + wiki/(LLM-Wiki 管线)+ wiki_router.py + external_provider.py(外接接缝) |
-| License | `core/licensing/` | features.py(能力位+402)、manager.py(状态机) |
-| LLM | `core/llm/` | agent_factory(含 turbo 参数), chat_models, middlewares, mcp_manager/mcp_pool, offloader, subagent_tool, tools/ |
-| 记忆 | `core/memory/` | service.py(mem0)、pipeline.py、profile.py(L1)、sanitizer.py、audit.py |
-| 本体 | `core/ontology/` | Domain Pack 校验、运行时选取、工具闸(schemas/validator/revision/toolkit) |
-| 沙箱 | `core/sandbox/` | protocol.py + script_runner / opensandbox / cube 三实现 |
-| 服务 | `core/services/` | 70+ 业务服务(user/chat/chat_mode/channel/loop/kb/plugin/prompt_version/marketplace…) |
-| 存储 | `core/storage/` | protocol, local, s3, oss |
-| 编排 | `orchestration/` | workflow, chat_run_executor, streaming, citation_anchor, autonomous_loop, followups, batch_orchestrator, schedulers/, subagents/ |
-| 提示词 | `prompts/` | prompt_runtime + provider + prompt_text/{default,code_exec,distillation,plan_mode,subagents,turbo}/ |
-| MCP 服务器 | `mcp_servers/` | 12 个 server 目录、11 个在役(端口 9100–9114,真源 _ports.py;9105/9109-9111 为退役保留位) |
-| 技能资产 | `skill_bundles/` | default/(5 内置)+ marketplace/(40+ 可安装) |
-| EE 树 | `edition_ee/` | EE 专属路由/模型/KB provider(dify/fastgpt/weknora)/evolution 控制面;CE 派生树物理删除 |
-
-## 新路由注册步骤
-
-1. 创建 `api/routes/v1/my_feature.py`(EE 专属则放 `edition_ee/routes/`),文件内定义 `router = APIRouter(prefix="/v1/xxx", tags=["Xxx"])`
-2. 注册进对应注册表:
- - 通用功能 → `api/routes/v1/__init__.py` 的 `CE_ROUTERS` 加 `("my_feature", "router")`
- - 企业版功能 → `edition_ee/routes/registry.py` 的 `EE_ROUTERS` 加 `("edition_ee.routes.my_feature", "router", "")`(能力位 `None` 表示显式豁免 feature 守卫)
-3. **不要**在 `api/app.py` 手工 `include_router()` —— app 启动时经 `iter_edition_routers()` 按表自动注册,模块缺失(CE 派生树物理删除了 EE 文件)会被静默跳过
+平级编排入口(不走普通回合):
+
+| 入口 | 位置 |
+|---|---|
+| 计划模式 | `orchestration/plans/`(generate_run / execute_run / mode) |
+| 自主循环 | `orchestration/loops/`(driver / planner / reviewer / requirements / recovery) |
+| 定时任务派发 | `orchestration/automations/` + `features/automation` |
+| 周期任务 / 恢复 | `orchestration/workers/`(`_worker_base.py`、run_recovery、memory_ttl、evolution、蒸馏 cron) |
+| 渠道入站 | `integrations/channels/`(归一为入站消息后复用聊天回合) |
+
+## 6. 数据访问与事件循环
+
+- 同步 SQLAlchemy Session 的每次查询都是阻塞 I/O。
+- **只用 Session 不 await 的路由写 `def`**(FastAPI 线程池执行);`def` 路由里没有事件循环,不能 `create_task` / `get_running_loop`。
+- **要 await 网络的 async 代码不持有 Session**:每段 DB 工作写成同步函数,用 `await run_in_session(fn, ...)`(新开并关闭会话)
+ 或 `await run_db(fn, ...)`(已有会话)在线程里跑;需要 async 接口的纯 DB 函数用 `@off_loop` 包装。
+- 线程池与连接池按 `core.db.engine.thread_budget()` 同一预算配置(`apply_thread_budget()`)。
+- 跨请求存活的工作:落库交给 worker(`orchestration/workers/_worker_base.py`);事件循环上的临时任务用 `core.infra.background.spawn(coro, name=...)`。
+- 守卫测试:`tests/test_no_blocking_async_routes.py`、`tests/test_no_event_loop_in_sync_routes.py`、
+ `tests/infra/test_no_session_on_event_loop.py`(全仓零例外:任何 async 函数都不收 `db` / `Session` / `Depends(get_db)`、
+ 不调 `SessionLocal()`、不 `with … as db`;测试里覆盖 `get_db` 时同时用 `tests/db_support.py` 让 `run_in_session` 指向同一个库)。
+
+## 7. 模块索引(常用入口)
+
+| 主题 | 路径 |
+|---|---|
+| 应用组装 | `api/app.py`、`api/lifecycle.py`、`api/startup/` |
+| 路由注册表 | `api/routes/v1/__init__.py`(CE)、`edition_ee/routes/registry.py`(EE)、`core/harness/registry`(模块路由) |
+| 依赖注入 | `api/deps.py`(require_admin / require_config / require_admin_or_config / require_system_settings / require_ontology_governance / require_super_admin / console_actor) |
+| 认证 | `core/auth/backend.py`(get_current_user / UserContext)、`core/auth/bearer.py` |
+| DB | `core/db/engine.py`(get_db)、`core/db/threads.py`、`core/db/models/`、`core/db/repository/` |
+| 信封 / 异常 | `core/infra/responses.py`、`core/infra/exceptions.py`、`api/middleware/error_handler.py` |
+| 配置 | `core/config/settings.py`、`core/config/catalog.json` |
+| 智能体回合 | `core/agent_runtime/`、`core/llm/factory/`、`core/llm/runtime/` |
+| 运行与续播 | `core/runs/`、`core/chat/runtime/`、`api/transports/run_sse.py` |
+| MCP 服务 | `extensions/mcp_servers/`(`registry.py`、`_ports.py` 端口稳定契约、`_launcher.py`)、`core/config/mcp_config.py` |
+| 技能 / 插件 / 智能体资源 | `extensions/{skills,plugins,agents}/{default,marketplace}`、`core/capabilities/` |
+| 提示词 | `prompts/prompt_runtime.py`(DB 版本池优先,`prompt_text/` 兜底)、`core/llm/prompt_*.py` |
+| License | `edition_ee/licensing/`(features / deps / gate / manager)、`api/middleware/edition.py` |
+| CE 派生 | `ce/manifest.yaml`(排除)、`ce/overlay/`(覆盖接缝文件) |
diff --git a/.claude/skills/hugagent-backend-dev/references/error-codes.md b/.claude/skills/hugagent-backend-dev/references/error-codes.md
index 18149924..73d26851 100644
--- a/.claude/skills/hugagent-backend-dev/references/error-codes.md
+++ b/.claude/skills/hugagent-backend-dev/references/error-codes.md
@@ -35,6 +35,8 @@
|------|------|----------------|------|
| 40001 | 404 | `ResourceNotFoundError` | 资源未找到 |
| 40002 | 404 | `EndpointNotFoundError` | API 端点不存在 |
+| 40201 | 402 | `FeatureNotLicensed`(`edition_ee/licensing/features.py`) | License 未授权该能力(EE 路由能力位守卫) |
+| 40202 | 402 | `SeatLimitExceeded`(同上) | 席位不足 / License 无效,无法新增用户 |
| 41001 | 409 | `ResourceAlreadyExistsError` | 资源已存在 |
| 41002 | 409 | `ConcurrentModificationError` | 并发修改冲突 |
| 42001 | 429 | `RateLimitExceededError` | 速率限制 |
diff --git a/.claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh b/.claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh
index 313654df..ff2ca623 100755
--- a/.claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh
+++ b/.claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh
@@ -1,83 +1,108 @@
#!/usr/bin/env bash
#
-# 后端新功能脚手架生成器
+# 后端功能模块脚手架:生成 features// 完整骨架
#
# 用法:
-# bash .claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh
+# bash .claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh
#
# 示例:
# bash .claude/skills/hugagent-backend-dev/scripts/scaffold_feature.sh bookmark Bookmark bookmarks
#
-# 生成文件:
-# - src/backend/api/routes/v1/s.py (路由)
-# - src/backend/core/services/_service.py (服务)
-# - src/backend/tests/test_.py (测试)
+# 生成:
+# src/backend/features//__init__.py
+# src/backend/features//README.md 职责、公开入口、依赖方向
+# src/backend/features//extension.py NAME + setup()
+# src/backend/features//models.py ORM(UTCDateTime + utc_now + JSONType)
+# src/backend/features//repository.py
+# src/backend/features//service.py
+# src/backend/features//api/__init__.py
+# src/backend/features//api/s.py 路由(同步 def + run_in_session 示例)
+# src/backend/tests/features//__init__.py
+# src/backend/tests/features//test_.py
#
-# 注意: model 和 repository 需手动合并到对应领域文件中
-# - core/db/models/<领域>.py ← 添加 ORM 类(并在包 __init__.py re-export)
-# - core/db/repository/<领域>.py ← 添加 Repository 类
-# - api/routes/v1/__init__.py ← 注册进 CE_ROUTERS / EE_ROUTERS 注册表
-# - 运行: alembic revision --autogenerate -m "add ${table_name} table"
+# 只建新文件,已存在的文件跳过;不改任何注册表(手动步骤见脚本末尾输出)。
+# 不是所有新功能都该进 features/:先按 SKILL.md「放哪里」判断。
set -euo pipefail
-FEATURE="${1:?Usage: $0 }"
-FEATURE_CAP="${2:?Usage: $0 }"
-TABLE="${3:?Usage: $0 }"
+FEATURE="${1:?Usage: $0 }"
+FEATURE_CAP="${2:?Usage: $0 }"
+TABLE="${3:?Usage: $0 }"
+FEATURE_UPPER="$(echo "$FEATURE" | tr '[:lower:]' '[:upper:]')"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TEMPLATE_DIR="$SCRIPT_DIR/../templates"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../../" && pwd)"
BACKEND="$PROJECT_ROOT/src/backend"
+PKG="$BACKEND/features/$FEATURE"
+TESTS="$BACKEND/tests/features/$FEATURE"
-echo "=== HugAgentOS Backend Scaffold ==="
-echo "Feature: $FEATURE / $FEATURE_CAP"
-echo "Table: $TABLE"
-echo ""
-
-# Route file
-ROUTE_FILE="$BACKEND/api/routes/v1/${FEATURE}s.py"
-if [ ! -f "$ROUTE_FILE" ]; then
+render() { # render
+ local src="$1" dest="$2"
+ if [ -f "$dest" ]; then
+ echo "⊘ Skipped ${dest#$PROJECT_ROOT/} (exists)"
+ return
+ fi
sed -e "s/\${feature}/$FEATURE/g" \
-e "s/\${Feature}/$FEATURE_CAP/g" \
- -e "s/\${FEATURE_NAME}/$FEATURE_CAP/g" \
- "$TEMPLATE_DIR/route.py" > "$ROUTE_FILE"
- echo "✓ Created $ROUTE_FILE"
-else
- echo "⊘ Skipped $ROUTE_FILE (exists)"
-fi
+ -e "s/\${FEATURE}/$FEATURE_UPPER/g" \
+ -e "s/\${table_name}/$TABLE/g" \
+ "$src" > "$dest"
+ echo "✓ Created ${dest#$PROJECT_ROOT/}"
+}
-# Service file
-SERVICE_FILE="$BACKEND/core/services/${FEATURE}_service.py"
-if [ ! -f "$SERVICE_FILE" ]; then
- sed -e "s/\${feature}/$FEATURE/g" \
- -e "s/\${Feature}/$FEATURE_CAP/g" \
- "$TEMPLATE_DIR/service.py" > "$SERVICE_FILE"
- echo "✓ Created $SERVICE_FILE"
-else
- echo "⊘ Skipped $SERVICE_FILE (exists)"
-fi
-
-# Test file
-TEST_FILE="$BACKEND/tests/test_${FEATURE}.py"
-if [ ! -f "$TEST_FILE" ]; then
- sed -e "s/\${feature}/$FEATURE/g" \
- -e "s/\${Feature}/$FEATURE_CAP/g" \
- "$TEMPLATE_DIR/test.py" > "$TEST_FILE"
- echo "✓ Created $TEST_FILE"
-else
- echo "⊘ Skipped $TEST_FILE (exists)"
-fi
+touch_file() { # touch_file
+ if [ -f "$1" ]; then
+ echo "⊘ Skipped ${1#$PROJECT_ROOT/} (exists)"
+ else
+ printf '%s\n' "$2" > "$1"
+ echo "✓ Created ${1#$PROJECT_ROOT/}"
+ fi
+}
+echo "=== HugAgentOS backend feature scaffold ==="
+echo "Feature: $FEATURE / $FEATURE_CAP Table: $TABLE"
echo ""
-echo "=== 手动步骤 ==="
-echo "1. 将 model 模板合并到 core/db/models/<领域>.py(替换 \${Feature}=$FEATURE_CAP, \${table_name}=$TABLE),并在包 __init__.py re-export"
-echo "2. 将 repository 模板合并到 core/db/repository/<领域>.py(替换 \${Feature}=$FEATURE_CAP)"
-echo "3. 在 api/routes/v1/__init__.py 的注册表中添加:"
-echo " CE_ROUTERS += ((\"${FEATURE}s\", \"router\"),) # 或 EE_ROUTERS(带 license 能力位)"
-echo "4. 创建迁移:"
-echo " alembic revision --autogenerate -m \"add $TABLE table\""
-echo "5. 应用迁移:"
-echo " alembic upgrade head"
-echo ""
-echo "Done!"
+
+mkdir -p "$PKG/api" "$TESTS"
+
+touch_file "$PKG/__init__.py" "\"\"\"${FEATURE_CAP} feature module.\"\"\""
+touch_file "$PKG/api/__init__.py" ""
+touch_file "$TESTS/__init__.py" ""
+touch_file "$PKG/README.md" "# ${FEATURE_CAP}
+
+负责:(一句话写清本模块拥有的业务能力与数据)。
+
+公开入口:service、repository、api/${FEATURE}s.py。调用方直接依赖实际模块,不保留旧路径转发。
+
+依赖方向:本模块依赖 core(db / auth / infra / harness registry);core 不导入本模块,只通过 extension.setup()
+注册的钩子接入。模型由安装清单加载,不依赖运行开关。
+
+版本差异:如 CE/EE 行为不同,统一放 edition.py,CE 只覆盖该入口。"
+
+render "$TEMPLATE_DIR/extension.py" "$PKG/extension.py"
+render "$TEMPLATE_DIR/model.py" "$PKG/models.py"
+render "$TEMPLATE_DIR/repository.py" "$PKG/repository.py"
+render "$TEMPLATE_DIR/service.py" "$PKG/service.py"
+render "$TEMPLATE_DIR/route.py" "$PKG/api/${FEATURE}s.py"
+render "$TEMPLATE_DIR/test.py" "$TESTS/test_${FEATURE}.py"
+
+cat < bool called once at startup; register hooks/routers/workers and return True,
+ or register nothing and return False when switched off (env flag etc.)
+ - Optional hooks the host looks up by name:
+ register_tools() agent tool contributions (product tool composition)
+ register_gateway_contributions() desktop gateway upload/receipt channels
+ register_catalog_options() capability catalog options
+ register_retrieval_context() retrieval context providers
+ - models.py is imported for every INSTALLED_FEATURES entry regardless of setup() — schema registration
+ must not depend on runtime switches
+
+Core never imports a feature's implementation: features call core.harness.registry.* to plug in.
+"""
+
+import os
+
+NAME = "${feature}"
+
+
+def enabled() -> bool:
+ return os.getenv("${FEATURE}_ENABLED", "true").strip().lower() in ("1", "true", "yes", "on")
+
+
+def setup() -> bool:
+ if not enabled():
+ return False
+ # Routes that must disappear when the module is off are mounted here instead of CE_ROUTERS.
+ # Always-on routes go into api/routes/v1/__init__.py::CE_ROUTERS (ordering is explicit there).
+ from core.harness.registry import register_router
+ from features.${feature}.api.${feature}s import router
+
+ register_router(router) # optional policy_key="content_admin" etc. applies the EE edition gate
+ return True
diff --git a/.claude/skills/hugagent-backend-dev/templates/model.py b/.claude/skills/hugagent-backend-dev/templates/model.py
index b591cc07..d3ad9d55 100644
--- a/.claude/skills/hugagent-backend-dev/templates/model.py
+++ b/.claude/skills/hugagent-backend-dev/templates/model.py
@@ -1,74 +1,58 @@
"""ORM model template.
Replace ${Feature}, ${feature}, ${table_name} with actual names.
-Copy relevant parts into core/db/models/<领域>.py (并在包 __init__.py re-export).
+
+Where it goes:
+ - Feature module → features/${feature}/models.py (loaded via INSTALLED_FEATURES by app and alembic)
+ - Core domain → core/db/models/.py and re-export in core/db/models/__init__.py
+ - EE-only table → edition_ee/db/models/… and add the table name to edition_ee/db/edition_tables.py::EE_ONLY_TABLES
+
+Conventions enforced by tests (tests/test_utc_conventions.py, tests/test_db_import_order.py):
+ - Timestamps: UTCDateTime(timezone=True) + default=utc_now (never datetime.utcnow / naive datetimes)
+ - JSON: JSONType (JSON on SQLite, JSONB on PostgreSQL) — models must work on both
+ - Soft delete via deleted_at; FKs declare ondelete
+ - Model modules import only core.db / core.infra leaves, never services or routes
"""
-from datetime import datetime
-from sqlalchemy import (
- Column, String, Integer, Boolean, Text, TIMESTAMP,
- ForeignKey, CheckConstraint, Index,
-)
-from sqlalchemy.dialects.postgresql import JSONB
-from sqlalchemy.orm import relationship
from core.db.engine import Base
+from core.db.models.column_types import JSONType
+from core.db.utc_datetime import UTCDateTime
+from core.infra.time import utc_now
+from sqlalchemy import Boolean, CheckConstraint, Column, ForeignKey, Index, Integer, String, Text
class ${Feature}(Base):
- """${Feature} table."""
+ """${Feature} — one sentence on what a row represents and who owns it."""
+
__tablename__ = "${table_name}"
- # --- Primary key ---
id = Column(String(64), primary_key=True)
-
- # --- Foreign key (to users_shadow) ---
user_id = Column(
String(64),
ForeignKey("users_shadow.user_id", ondelete="CASCADE"),
nullable=False,
)
+ # Optional link that must survive parent deletion → SET NULL
+ project_id = Column(
+ String(64),
+ ForeignKey("projects.project_id", ondelete="SET NULL"),
+ nullable=True,
+ )
- # --- Business fields ---
name = Column(String(200), nullable=False)
description = Column(Text)
- status = Column(String(20), nullable=False, default="active")
- count = Column(Integer, default=0)
- is_active = Column(Boolean, default=True)
-
- # --- Flexible JSONB ---
- extra_data = Column("metadata", JSONB, default={})
+ position = Column(Integer, nullable=False, default=0)
+ is_enabled = Column(Boolean, nullable=False, default=True)
- # --- Soft delete ---
- deleted_at = Column(TIMESTAMP(timezone=True))
-
- # --- Timestamps (required) ---
- created_at = Column(TIMESTAMP(timezone=True), default=datetime.utcnow)
- updated_at = Column(
- TIMESTAMP(timezone=True),
- default=datetime.utcnow,
- onupdate=datetime.utcnow,
- )
+ # Python attribute extra_data, DB column "metadata" (metadata is reserved on declarative models)
+ extra_data = Column("metadata", JSONType, default=dict)
- # --- Relationships ---
- user = relationship("UserShadow", back_populates="${feature}s")
+ created_at = Column(UTCDateTime(timezone=True), default=utc_now)
+ updated_at = Column(UTCDateTime(timezone=True), default=utc_now, onupdate=utc_now)
+ deleted_at = Column(UTCDateTime(timezone=True))
- # --- Indexes & Constraints ---
__table_args__ = (
- CheckConstraint("count >= 0", name="${table_name}_count_check"),
+ CheckConstraint("position >= 0", name="${table_name}_position_check"),
Index("idx_${table_name}_user_id", "user_id"),
Index("idx_${table_name}_updated_at", "updated_at"),
- Index(
- "idx_${table_name}_user_updated",
- "user_id", "updated_at",
- ),
- Index(
- "idx_${table_name}_deleted",
- "deleted_at",
- postgresql_where=Column("deleted_at").isnot(None),
- ),
- Index(
- "idx_${table_name}_metadata_gin",
- "metadata",
- postgresql_using="gin",
- ),
)
diff --git a/.claude/skills/hugagent-backend-dev/templates/repository.py b/.claude/skills/hugagent-backend-dev/templates/repository.py
index fdebaa87..fa006a57 100644
--- a/.claude/skills/hugagent-backend-dev/templates/repository.py
+++ b/.claude/skills/hugagent-backend-dev/templates/repository.py
@@ -1,83 +1,57 @@
-"""Repository template.
+"""Repository template — data access only, no business rules.
Replace ${Feature}, ${feature} with actual names.
-Add to core/db/repository/<领域>.py.
+Feature module: features/${feature}/repository.py. Core domain: core/db/repository/.py.
+
+Rules:
+ - Synchronous; takes a Session. Called from sync routes or inside core.db.threads.run_in_session
+ - Every read filters deleted_at IS NULL
+ - Time via core.infra.time.utc_now
"""
-from typing import Optional, List, Dict, Any, Tuple
-from datetime import datetime
-from sqlalchemy.orm import Session
+from typing import Any, Dict, List, Optional, Tuple
+
+from core.infra.time import utc_now
+from features.${feature}.models import ${Feature}
from sqlalchemy import desc
-from core.db.models import ${Feature}
+from sqlalchemy.orm import Session
class ${Feature}Repository:
- """Repository for ${feature} operations."""
-
def __init__(self, db: Session):
self.db = db
- # --- Read ---
+ def _live(self):
+ return self.db.query(${Feature}).filter(${Feature}.deleted_at.is_(None))
- def get_by_id(self, id: str) -> Optional[${Feature}]:
- """Get by primary key (respects soft delete)."""
- return self.db.query(${Feature}).filter(
- ${Feature}.id == id,
- ${Feature}.deleted_at.is_(None),
- ).first()
+ def get_by_id(self, item_id: str) -> Optional[${Feature}]:
+ return self._live().filter(${Feature}.id == item_id).first()
- def list_by_user(
- self,
- user_id: str,
- page: int = 1,
- page_size: int = 20,
- ) -> Tuple[List[${Feature}], int]:
- """Paginated list for user."""
- query = self.db.query(${Feature}).filter(
- ${Feature}.user_id == user_id,
- ${Feature}.deleted_at.is_(None),
- )
+ def list_by_user(self, user_id: str, page: int = 1, page_size: int = 20) -> Tuple[List[${Feature}], int]:
+ query = self._live().filter(${Feature}.user_id == user_id)
total = query.count()
items = (
- query
- .order_by(desc(${Feature}.updated_at))
+ query.order_by(desc(${Feature}.updated_at))
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return items, total
- # --- Create ---
-
def create(self, data: Dict[str, Any]) -> ${Feature}:
- """Create and commit."""
item = ${Feature}(**data)
self.db.add(item)
self.db.commit()
self.db.refresh(item)
return item
- # --- Update ---
-
- def update(self, id: str, data: Dict[str, Any]) -> Optional[${Feature}]:
- """Update fields and commit."""
- item = self.get_by_id(id)
- if not item:
- return None
+ def update(self, item: ${Feature}, data: Dict[str, Any]) -> ${Feature}:
for key, value in data.items():
setattr(item, key, value)
- item.updated_at = datetime.utcnow()
self.db.commit()
self.db.refresh(item)
return item
- # --- Delete (soft) ---
-
- def soft_delete(self, id: str) -> bool:
- """Mark as deleted."""
- item = self.get_by_id(id)
- if not item:
- return False
- item.deleted_at = datetime.utcnow()
+ def soft_delete(self, item: ${Feature}) -> None:
+ item.deleted_at = utc_now()
self.db.commit()
- return True
diff --git a/.claude/skills/hugagent-backend-dev/templates/route.py b/.claude/skills/hugagent-backend-dev/templates/route.py
index 279a688d..d584e541 100644
--- a/.claude/skills/hugagent-backend-dev/templates/route.py
+++ b/.claude/skills/hugagent-backend-dev/templates/route.py
@@ -1,136 +1,126 @@
-"""${FEATURE_NAME} API routes (v1).
-
-Replace ${FEATURE_NAME}, ${feature}, ${Feature} with actual names.
+"""API route template.
+
+Replace ${Feature}, ${feature} with actual names.
+Feature module: features/${feature}/api/${feature}s.py, registered in api/routes/v1/__init__.py::CE_ROUTERS as
+("features.${feature}.api.${feature}s", "router") — or in edition_ee/routes/registry.py::EE_ROUTERS with a policy key,
+or via register_router() from features/${feature}/extension.py when the module can be switched off.
+
+Event-loop rules (enforced by tests/test_no_blocking_async_routes.py, tests/test_no_event_loop_in_sync_routes.py,
+tests/infra/test_no_session_on_event_loop.py):
+ 1. Endpoint uses a Session and awaits nothing → declare it `def` (FastAPI runs it in the thread pool).
+ 2. Endpoint must await network I/O → `async def` WITHOUT `db: Session = Depends(get_db)`; put each DB step in a
+ sync helper and call it with `await run_in_session(helper, ...)`. Never hold a Session across an await.
+ 3. A `def` endpoint has no event loop: no asyncio.create_task / get_running_loop. Durable work → persist and let a
+ worker pick it up (orchestration/workers/_worker_base.py); loop-bound fire-and-forget → core.infra.background.spawn.
+
+Module must import on its own without composing the app (tests/test_route_import_order.py).
"""
-from typing import Any, Dict, List, Optional
+from typing import Optional
+from core.auth.backend import UserContext, get_current_user
+from core.db.engine import get_db
+from core.db.threads import run_in_session
+from core.infra.responses import created_response, paginated_response, success_response
from fastapi import APIRouter, Depends, Query, status
-from pydantic import BaseModel, Field
+from features.${feature}.service import ${Feature}Service
+from pydantic import BaseModel, Field, field_validator
from sqlalchemy.orm import Session
-from api.deps import require_admin
-from core.auth.backend import get_current_user, UserContext
-from core.db.engine import get_db
-from core.infra.exceptions import ResourceNotFoundError, BadRequestError
-from core.infra.logging import get_logger
-from core.infra.responses import success_response, created_response, paginated_response
-from core.services.${feature}_service import ${Feature}Service
-
-logger = get_logger(__name__)
+router = APIRouter(prefix="/v1/${feature}s", tags=["${Feature}"])
-router = APIRouter(prefix="/v1/${feature}s", tags=["${Feature}s"])
-
-
-# ---------------------------------------------------------------------------
-# Request / Response Models
-# ---------------------------------------------------------------------------
class Create${Feature}Request(BaseModel):
- """创建 ${Feature} 请求体."""
- name: str = Field(..., description="名称", min_length=1, max_length=200)
- description: Optional[str] = Field(None, description="描述")
- metadata: Optional[dict] = Field(default_factory=dict, description="扩展信息")
+ name: str = Field(..., min_length=1, max_length=200, description="名称")
+ description: Optional[str] = Field(None, max_length=2000, description="说明")
+ @field_validator("name")
+ @classmethod
+ def _strip(cls, v: str) -> str:
+ return v.strip()
-class Update${Feature}Request(BaseModel):
- """更新 ${Feature} 请求体."""
- name: Optional[str] = Field(None, description="名称", max_length=200)
- description: Optional[str] = Field(None, description="描述")
- metadata: Optional[dict] = Field(None, description="扩展信息")
+class Update${Feature}Request(BaseModel):
+ name: Optional[str] = Field(None, min_length=1, max_length=200)
+ description: Optional[str] = Field(None, max_length=2000)
+ is_enabled: Optional[bool] = None
-# ---------------------------------------------------------------------------
-# Helpers
-# ---------------------------------------------------------------------------
def _to_dict(item) -> dict:
- """ORM → API response dict."""
return {
"id": item.id,
"name": item.name,
"description": item.description,
- "metadata": item.extra_data or {},
+ "is_enabled": item.is_enabled,
"created_at": item.created_at.isoformat() if item.created_at else None,
"updated_at": item.updated_at.isoformat() if item.updated_at else None,
}
-# ---------------------------------------------------------------------------
-# Endpoints
-# ---------------------------------------------------------------------------
+# --- Pure DB endpoints: plain `def` -----------------------------------------------------------
+
-@router.get("", summary="获取${Feature}列表")
-async def list_items(
+@router.get("", summary="列出我的${Feature}")
+def list_items(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
user: UserContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
- service = ${Feature}Service(db)
- items, total, total_pages = service.list_items(user.user_id, page, page_size)
+ items, total = ${Feature}Service(db).list_items(str(user.user_id), page, page_size)
return paginated_response(
- items=[_to_dict(i) for i in items],
- page=page,
- page_size=page_size,
- total_items=total,
+ items=[_to_dict(i) for i in items], page=page, page_size=page_size, total_items=total
)
@router.get("/{item_id}", summary="获取${Feature}详情")
-async def get_item(
- item_id: str,
- user: UserContext = Depends(get_current_user),
- db: Session = Depends(get_db),
-):
- service = ${Feature}Service(db)
- item = service.get_item(item_id, user.user_id)
- if not item:
- raise ResourceNotFoundError("${feature}", item_id)
- return success_response(data=_to_dict(item))
+def get_item(item_id: str, user: UserContext = Depends(get_current_user), db: Session = Depends(get_db)):
+ return success_response(data=_to_dict(${Feature}Service(db).get_owned(item_id, str(user.user_id))))
-@router.post("", status_code=status.HTTP_201_CREATED, summary="创建${Feature}")
-async def create_item(
+@router.post("", status_code=status.HTTP_201_CREATED, summary="新建${Feature}")
+def create_item(
body: Create${Feature}Request,
user: UserContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
- service = ${Feature}Service(db)
- item = service.create(
- user_id=user.user_id,
- name=body.name,
- description=body.description,
- metadata=body.metadata,
- )
+ item = ${Feature}Service(db).create(str(user.user_id), body.name, body.description)
return created_response(data=_to_dict(item))
-@router.put("/{item_id}", summary="更新${Feature}")
-async def update_item(
+@router.patch("/{item_id}", summary="修改${Feature}")
+def update_item(
item_id: str,
body: Update${Feature}Request,
user: UserContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
- service = ${Feature}Service(db)
- update_data = body.model_dump(exclude_none=True)
- if not update_data:
- raise BadRequestError("No fields to update")
- item = service.update(item_id, user.user_id, update_data)
- if not item:
- raise ResourceNotFoundError("${feature}", item_id)
+ item = ${Feature}Service(db).update(item_id, str(user.user_id), body.model_dump(exclude_unset=True))
return success_response(data=_to_dict(item))
@router.delete("/{item_id}", summary="删除${Feature}")
-async def delete_item(
- item_id: str,
- user: UserContext = Depends(get_current_user),
- db: Session = Depends(get_db),
-):
- service = ${Feature}Service(db)
- ok = service.delete(item_id, user.user_id)
- if not ok:
- raise ResourceNotFoundError("${feature}", item_id)
- return success_response(message="Deleted")
+def delete_item(item_id: str, user: UserContext = Depends(get_current_user), db: Session = Depends(get_db)):
+ ${Feature}Service(db).delete(item_id, str(user.user_id))
+ return success_response(data={"id": item_id})
+
+
+# --- Endpoint that awaits network I/O: async, DB steps off the loop ---------------------------
+
+
+def _load_for_sync(db: Session, item_id: str, user_id: str) -> dict:
+ return _to_dict(${Feature}Service(db).get_owned(item_id, user_id))
+
+
+def _record_sync_result(db: Session, item_id: str, user_id: str, remote: dict) -> dict:
+ item = ${Feature}Service(db).update(item_id, user_id, {"description": remote.get("summary")})
+ return _to_dict(item)
+
+
+@router.post("/{item_id}/sync", summary="与外部系统同步")
+async def sync_item(item_id: str, user: UserContext = Depends(get_current_user)):
+ user_id = str(user.user_id)
+ current = await run_in_session(_load_for_sync, item_id, user_id) # DB step 1, session closed after
+ remote = {"summary": current["name"]} # await some_client.fetch(...) here
+ data = await run_in_session(_record_sync_result, item_id, user_id, remote) # DB step 2
+ return success_response(data=data)
diff --git a/.claude/skills/hugagent-backend-dev/templates/service.py b/.claude/skills/hugagent-backend-dev/templates/service.py
index a2e22666..5d02f65f 100644
--- a/.claude/skills/hugagent-backend-dev/templates/service.py
+++ b/.claude/skills/hugagent-backend-dev/templates/service.py
@@ -1,108 +1,60 @@
-"""Service layer template.
+"""Service template — business rules and permission checks.
Replace ${Feature}, ${feature} with actual names.
-Create as core/services/${feature}_service.py.
+Feature module: features/${feature}/service.py. Core domain: core//.py
+(there is no core/services/ package any more — services live with the domain that owns them).
+
+Rules:
+ - Synchronous; constructor takes a Session and builds its repositories
+ - Raises core.infra.exceptions.* (never HTTPException, never returns error dicts)
+ - Ownership / permission checks live here, not in the route
+ - No network I/O inside a method that holds the Session: do DB work, return, await network
+ in the async caller, then open a new DB step (core.db.threads)
+ - Module ≤ 500 lines; split by responsibility before it grows past that
"""
import uuid
-from typing import Optional, List, Dict, Any, Tuple
-from sqlalchemy.orm import Session
+from typing import List, Optional, Tuple
-from core.db.repository import ${Feature}Repository, AuditLogRepository
-from core.db.models import ${Feature}
-from core.infra.exceptions import ResourceOwnershipError
+from core.infra.exceptions import BadRequestError, ResourceNotFoundError, ResourceOwnershipError
+from features.${feature}.models import ${Feature}
+from features.${feature}.repository import ${Feature}Repository
+from sqlalchemy.orm import Session
class ${Feature}Service:
- """Business logic for ${feature}s."""
-
def __init__(self, db: Session):
self.db = db
self.repo = ${Feature}Repository(db)
- self.audit_repo = AuditLogRepository(db)
-
- # --- Create ---
-
- def create(
- self,
- user_id: str,
- name: str,
- description: str = None,
- metadata: dict = None,
- ) -> ${Feature}:
- """Create a new ${feature}."""
- item = self.repo.create({
- "id": f"${feature}_{uuid.uuid4().hex[:16]}",
- "user_id": user_id,
- "name": name,
- "description": description,
- "extra_data": metadata or {},
- })
-
- self.audit_repo.create({
- "user_id": user_id,
- "action": "${feature}.created",
- "resource_type": "${feature}",
- "resource_id": item.id,
- "status": "success",
- })
-
- return item
-
- # --- Read ---
- def get_item(self, id: str, user_id: str) -> Optional[${Feature}]:
- """Get item with ownership check."""
- item = self.repo.get_by_id(id)
- if item and item.user_id != user_id:
- raise ResourceOwnershipError("${feature}", id)
- return item
-
- def list_items(
- self,
- user_id: str,
- page: int = 1,
- page_size: int = 20,
- ) -> Tuple[List[${Feature}], int, int]:
- """List with pagination. Returns (items, total, total_pages)."""
- items, total = self.repo.list_by_user(user_id, page, page_size)
- total_pages = (total + page_size - 1) // page_size
- return items, total, total_pages
-
- # --- Update ---
-
- def update(
- self,
- id: str,
- user_id: str,
- update_data: Dict[str, Any],
- ) -> Optional[${Feature}]:
- """Update with ownership check."""
- item = self.repo.get_by_id(id)
- if not item:
- return None
+ def get_owned(self, item_id: str, user_id: str) -> ${Feature}:
+ item = self.repo.get_by_id(item_id)
+ if item is None:
+ raise ResourceNotFoundError("${feature}", item_id)
if item.user_id != user_id:
- raise ResourceOwnershipError("${feature}", id)
- return self.repo.update(id, update_data)
-
- # --- Delete ---
-
- def delete(self, id: str, user_id: str) -> bool:
- """Soft delete with ownership check."""
- item = self.repo.get_by_id(id)
- if not item:
- return False
- if item.user_id != user_id:
- raise ResourceOwnershipError("${feature}", id)
- return self.repo.soft_delete(id)
-
- # --- Idempotent ensure ---
+ raise ResourceOwnershipError("${feature}", item_id)
+ return item
- def ensure(self, id: str, user_id: str, **defaults) -> Optional[${Feature}]:
- """Get existing or create (idempotent)."""
- existing = self.repo.get_by_id(id)
- if existing:
- if existing.user_id != user_id:
- return None
- return existing
- return self.create(user_id=user_id, **defaults)
+ def list_items(self, user_id: str, page: int, page_size: int) -> Tuple[List[${Feature}], int]:
+ return self.repo.list_by_user(user_id, page, page_size)
+
+ def create(self, user_id: str, name: str, description: Optional[str] = None) -> ${Feature}:
+ name = name.strip()
+ if not name:
+ raise BadRequestError("名称不能为空")
+ return self.repo.create(
+ {
+ "id": f"${feature}_{uuid.uuid4().hex[:16]}",
+ "user_id": user_id,
+ "name": name,
+ "description": description,
+ }
+ )
+
+ def update(self, item_id: str, user_id: str, changes: dict) -> ${Feature}:
+ item = self.get_owned(item_id, user_id)
+ allowed = {k: v for k, v in changes.items() if k in {"name", "description", "is_enabled"} and v is not None}
+ return self.repo.update(item, allowed)
+
+ def delete(self, item_id: str, user_id: str) -> None:
+ self.repo.soft_delete(self.get_owned(item_id, user_id))
diff --git a/.claude/skills/hugagent-backend-dev/templates/test.py b/.claude/skills/hugagent-backend-dev/templates/test.py
index aa456b7b..6f3b1a33 100644
--- a/.claude/skills/hugagent-backend-dev/templates/test.py
+++ b/.claude/skills/hugagent-backend-dev/templates/test.py
@@ -1,115 +1,86 @@
"""Test template.
Replace ${Feature}, ${feature} with actual names.
-Create as tests/test_${feature}.py.
-Run: PYTHONPATH=src/backend pytest src/backend/tests/test_${feature}.py -v
+Create as src/backend/tests/features/${feature}/test_${feature}.py (core domains: tests//test_*.py).
+Run: PYTHONPATH=src/backend pytest src/backend/tests/features/${feature} -q
+
+Rules:
+ - Test behaviour at a real boundary (service against a real DB session, or HTTP through the router);
+ do not assert internal call order
+ - The shared `db_session` fixture (tests/conftest.py) gives a fresh database per test (SQLite unless
+ TEST_DATABASE_URL points at PostgreSQL); schema changes should pass on both
+ - Write the failing test first; never weaken an assertion to make it pass
"""
import pytest
-from unittest.mock import MagicMock, patch
-from sqlalchemy import create_engine
-from sqlalchemy.orm import sessionmaker
+from core.infra.exceptions import BadRequestError, ResourceNotFoundError, ResourceOwnershipError
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from features.${feature}.service import ${Feature}Service
-from core.db.engine import Base
-from core.db.models import ${Feature}
-from core.db.repository import ${Feature}Repository
-from core.services.${feature}_service import ${Feature}Service
+@pytest.fixture
+def service(db_session):
+ return ${Feature}Service(db_session)
-# ---------------------------------------------------------------------------
-# Fixtures
-# ---------------------------------------------------------------------------
-@pytest.fixture
-def db_session():
- """In-memory SQLite session for testing."""
- engine = create_engine("sqlite:///:memory:")
- Base.metadata.create_all(engine)
- Session = sessionmaker(bind=engine)
- session = Session()
- yield session
- session.close()
- Base.metadata.drop_all(engine)
+def test_create_and_list(service):
+ created = service.create("user_a", " 周报 ")
+ assert created.name == "周报"
+ items, total = service.list_items("user_a", 1, 20)
+ assert total == 1 and items[0].id == created.id
-@pytest.fixture
-def service(db_session):
- """${Feature}Service with test DB session."""
- return ${Feature}Service(db_session)
+def test_rejects_blank_name(service):
+ with pytest.raises(BadRequestError):
+ service.create("user_a", " ")
+
+
+def test_other_users_cannot_read_or_delete(service):
+ item = service.create("user_a", "私有")
+ with pytest.raises(ResourceOwnershipError):
+ service.get_owned(item.id, "user_b")
+ with pytest.raises(ResourceOwnershipError):
+ service.delete(item.id, "user_b")
+
+
+def test_soft_delete_hides_item(service):
+ item = service.create("user_a", "待删")
+ service.delete(item.id, "user_a")
+ with pytest.raises(ResourceNotFoundError):
+ service.get_owned(item.id, "user_a")
+ assert service.list_items("user_a", 1, 20)[1] == 0
+
+
+# --- HTTP boundary: mount only this router, override auth and DB --------------------------------
@pytest.fixture
-def sample_data():
- """Sample creation data."""
- return {
- "user_id": "test_user_001",
- "name": "Test ${Feature}",
- "description": "Test description",
- }
-
-
-# ---------------------------------------------------------------------------
-# Repository Tests
-# ---------------------------------------------------------------------------
-
-class TestRepository:
- def test_create(self, db_session, sample_data):
- repo = ${Feature}Repository(db_session)
- item = repo.create({
- "id": "${feature}_test001",
- **sample_data,
- })
- assert item.id == "${feature}_test001"
- assert item.name == sample_data["name"]
- assert item.deleted_at is None
-
- def test_get_by_id(self, db_session):
- repo = ${Feature}Repository(db_session)
- repo.create({"id": "${feature}_001", "user_id": "u1", "name": "A"})
- found = repo.get_by_id("${feature}_001")
- assert found is not None
- assert found.name == "A"
-
- def test_get_by_id_respects_soft_delete(self, db_session):
- repo = ${Feature}Repository(db_session)
- repo.create({"id": "${feature}_del", "user_id": "u1", "name": "D"})
- repo.soft_delete("${feature}_del")
- assert repo.get_by_id("${feature}_del") is None
-
- def test_list_by_user_pagination(self, db_session):
- repo = ${Feature}Repository(db_session)
- for i in range(5):
- repo.create({"id": f"${feature}_{i}", "user_id": "u1", "name": f"Item {i}"})
- items, total = repo.list_by_user("u1", page=1, page_size=3)
- assert total == 5
- assert len(items) == 3
-
-
-# ---------------------------------------------------------------------------
-# Service Tests
-# ---------------------------------------------------------------------------
-
-class TestService:
- def test_create(self, service, sample_data):
- item = service.create(**sample_data)
- assert item.name == sample_data["name"]
- assert item.user_id == sample_data["user_id"]
- assert item.id.startswith("${feature}_")
-
- def test_get_item_ownership(self, service):
- item = service.create(user_id="u1", name="Mine")
- # Owner can access
- assert service.get_item(item.id, "u1") is not None
- # Non-owner raises
- with pytest.raises(Exception):
- service.get_item(item.id, "u2")
-
- def test_delete(self, service):
- item = service.create(user_id="u1", name="ToDelete")
- assert service.delete(item.id, "u1") is True
- assert service.get_item(item.id, "u1") is None
-
- def test_ensure_idempotent(self, service):
- item1 = service.create(user_id="u1", name="First")
- item2 = service.ensure(item1.id, "u1")
- assert item1.id == item2.id
+def client(db_session, monkeypatch):
+ from core.auth.backend import UserContext, get_current_user
+ from core.db.engine import get_db
+ from core.infra.exceptions import AppException
+ from features.${feature}.api.${feature}s import router
+ from fastapi.responses import JSONResponse
+ from tests.db_support import share_session
+
+ app = FastAPI()
+ app.include_router(router)
+ app.dependency_overrides[get_db] = lambda: db_session
+ # async routes reach the database through run_in_session, not get_db
+ share_session(monkeypatch, db_session)
+ app.dependency_overrides[get_current_user] = lambda: UserContext(user_id="user_a", user_center_id="uc_a", username="a")
+
+ @app.exception_handler(AppException)
+ async def _envelope(_, exc: AppException):
+ return JSONResponse(status_code=exc.status_code, content={"code": exc.code, "message": exc.message})
+
+ return TestClient(app)
+
+
+def test_http_create_returns_envelope(client):
+ resp = client.post("/v1/${feature}s", json={"name": "周报"})
+ assert resp.status_code == 201
+ body = resp.json()
+ assert body["code"] == 10001
+ assert body["data"]["name"] == "周报"
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/attachment.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/attachment.svg
new file mode 100644
index 00000000..029d1c30
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/attachment.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/automation.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/automation.svg
new file mode 100644
index 00000000..0663b72e
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/automation.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/check.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/check.svg
new file mode 100644
index 00000000..1bfd4b84
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/check.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/chevron-right.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/chevron-right.svg
new file mode 100644
index 00000000..068cb596
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/chevron-right.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/close.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/close.svg
new file mode 100644
index 00000000..b5d6b13d
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/close.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/knowledge.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/knowledge.svg
new file mode 100644
index 00000000..1221ffaa
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/knowledge.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/my-space.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/my-space.svg
new file mode 100644
index 00000000..d7b07e6a
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/my-space.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/new-chat.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/new-chat.svg
new file mode 100644
index 00000000..98a36618
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/new-chat.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/search.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/search.svg
new file mode 100644
index 00000000..263a0e98
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/search.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/send.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/send.svg
new file mode 100644
index 00000000..1eee11cf
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/send.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/settings.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/settings.svg
new file mode 100644
index 00000000..6d957f24
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/settings.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/skill.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/skill.svg
new file mode 100644
index 00000000..48244686
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/icons-ui/skill.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/favicon.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/favicon.svg
new file mode 100644
index 00000000..77e0a10a
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/favicon.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-black.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-black.svg
new file mode 100644
index 00000000..4ac67167
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-black.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-blue.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-blue.svg
new file mode 100644
index 00000000..f1b49091
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-blue.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-white.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-white.svg
new file mode 100644
index 00000000..5c8116ff
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-glyph-white.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-black.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-black.svg
new file mode 100644
index 00000000..2fa9be89
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-black.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-white.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-white.svg
new file mode 100644
index 00000000..198f5f3e
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-mono-white.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-reverse.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-reverse.svg
new file mode 100644
index 00000000..902c6bf0
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-reverse.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-small.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-small.svg
new file mode 100644
index 00000000..77e0a10a
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark-small.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark.svg b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark.svg
new file mode 100644
index 00000000..67a8da85
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/logo/hugagentos-mark.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/appTheme.v2.ts b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/appTheme.v2.ts
new file mode 100644
index 00000000..599b20a0
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/appTheme.v2.ts
@@ -0,0 +1,39 @@
+// HugAgentOS Ant Design tokens v2 — reference for src/frontend/src/appTheme.ts.
+// Differences from the current file: hover/active darken instead of lighten; dark primary stays #126DFF;
+// semantic text colours and placeholder meet 4.5:1.
+import type { ThemeConfig } from 'antd';
+import { theme as antdTheme } from 'antd';
+
+const shared = {
+ borderRadius: 10, borderRadiusLG: 16, borderRadiusSM: 6,
+ fontFamily: 'var(--font-family)', fontSize: 14, fontSizeSM: 12, fontSizeLG: 16,
+ colorWarning: '#F8AB42',
+};
+
+export const LIGHT_V2: ThemeConfig = {
+ algorithm: antdTheme.defaultAlgorithm,
+ token: {
+ ...shared,
+ colorPrimary: '#126DFF', colorPrimaryHover: '#0F62E6', colorPrimaryActive: '#0A56CC',
+ colorLink: '#0F62E6', colorInfo: '#126DFF',
+ colorSuccess: '#02B589', colorError: '#D92D20',
+ colorSuccessText: '#067A5E', colorWarningText: '#B45309', colorErrorText: '#D92D20',
+ colorText: '#101828', colorTextSecondary: '#475467', colorTextTertiary: '#667085', colorTextPlaceholder: '#667085',
+ colorBorder: '#C5CCD8', colorBorderSecondary: '#E3E8EF',
+ colorBgContainer: '#FFFFFF', colorBgLayout: '#F5F7FB', colorBgElevated: '#FFFFFF',
+ },
+};
+
+export const DARK_V2: ThemeConfig = {
+ algorithm: antdTheme.darkAlgorithm,
+ token: {
+ ...shared,
+ colorPrimary: '#126DFF', colorPrimaryHover: '#0F62E6', colorPrimaryActive: '#0A56CC',
+ colorLink: '#5C9BFF', colorInfo: '#126DFF',
+ colorSuccess: '#22C79D', colorError: '#FF6B6B',
+ colorSuccessText: '#3DDBB0', colorWarningText: '#FFC26B', colorErrorText: '#FF8A80',
+ colorText: '#E8ECF4', colorTextSecondary: '#B3BDCD', colorTextTertiary: '#8792A4', colorTextPlaceholder: '#8792A4',
+ colorBorder: '#3A4556', colorBorderSecondary: '#2B3442',
+ colorBgContainer: '#161C25', colorBgLayout: '#12171F', colorBgElevated: '#1C2330', colorBgSpotlight: '#1C2330',
+ },
+};
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.css b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.css
new file mode 100644
index 00000000..a74c2600
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.css
@@ -0,0 +1,123 @@
+/* HugAgentOS design tokens v2 — generated with the design guidelines (2026-10).
+ * Additive to src/frontend/src/styles/variables.css: new names only, existing names keep their values
+ * except where the guidelines' migration list says otherwise. */
+:root{
+ --color-primary:#126DFF;
+ --color-on-primary:#FFFFFF;
+ --color-primary-text:#0F62E6;
+ --color-primary-bg:#DBE9FF;
+ --color-primary-light:#EBF2FF;
+ --state-hover:rgba(16,24,40,.04);
+ --state-press:rgba(16,24,40,.08);
+ --state-on-fill-hover:rgba(0,0,0,.08);
+ --state-on-fill-press:rgba(0,0,0,.16);
+ --color-success:#02B589;
+ --color-success-text:#067A5E;
+ --color-success-bg:#E6FFF9;
+ --color-warning:#F8AB42;
+ --color-warning-text:#B45309;
+ --color-warning-bg:#FFF5E8;
+ --color-error:#FC5D5D;
+ --color-error-text:#D92D20;
+ --color-error-bg:#FFF0F0;
+ --color-danger-fill:#D92D20;
+ --color-text:#101828;
+ --color-text-secondary:#475467;
+ --color-text-tertiary:#667085;
+ --color-text-placeholder:#667085;
+ --color-separator:#E3E8EF;
+ --color-control-border:#C5CCD8;
+ --color-fill-quiet:#F2F4F7;
+ --color-bg-base:#FFFFFF;
+ --color-bg-container:#FFFFFF;
+ --color-bg-elevated:#FFFFFF;
+ --color-bg-layout:#F5F7FB;
+ --color-bg-chat:#FDFDFC;
+ --color-brand-spark:#19D3A7;
+ --material:rgba(255,255,255,.82);
+ --scrim:rgba(16,24,40,.32);
+ --color-tint-blue-bg:#DBEAFE;
+ --color-tint-blue:#1E40AF;
+ --color-tint-purple-bg:#EDE9FE;
+ --color-tint-purple:#6D28D9;
+ --color-tint-teal-bg:#CCFBF1;
+ --color-tint-teal:#0F766E;
+ --color-tint-amber-bg:#FEF3C7;
+ --color-tint-amber:#B45309;
+ --shadow-raised:0 1px 2px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.06);
+ --shadow-floating:0 2px 8px rgba(16,24,40,.06), 0 20px 48px rgba(16,24,40,.14);
+ --space-1:4px;
+ --space-2:8px;
+ --space-3:12px;
+ --space-4:16px;
+ --space-6:24px;
+ --space-8:32px;
+ --space-12:48px;
+ --space-16:64px;
+ --radius-xs:6px;
+ --radius-sm:10px;
+ --radius-md:16px;
+ --radius-lg:24px;
+ --radius-full:999px;
+ --font-size-display:2.5rem;--line-height-display:3rem;--font-weight-display:600;--letter-spacing-display:-0.02em;
+ --font-size-title-1:1.75rem;--line-height-title-1:2.25rem;--font-weight-title-1:600;--letter-spacing-title-1:-0.01em;
+ --font-size-title-2:1.375rem;--line-height-title-2:1.875rem;--font-weight-title-2:600;--letter-spacing-title-2:0;
+ --font-size-title-3:1.125rem;--line-height-title-3:1.625rem;--font-weight-title-3:600;--letter-spacing-title-3:0;
+ --font-size-headline:0.9375rem;--line-height-headline:1.375rem;--font-weight-headline:600;--letter-spacing-headline:0;
+ --font-size-body-lg:1rem;--line-height-body-lg:1.75rem;--font-weight-body-lg:400;--letter-spacing-body-lg:0;
+ --font-size-body:0.875rem;--line-height-body:1.375rem;--font-weight-body:400;--letter-spacing-body:0;
+ --font-size-footnote:0.8125rem;--line-height-footnote:1.25rem;--font-weight-footnote:400;--letter-spacing-footnote:0;
+ --font-size-caption:0.75rem;--line-height-caption:1.125rem;--font-weight-caption:400;--letter-spacing-caption:0;
+ --motion-duration-instant:100ms;
+ --motion-duration-fast:160ms;
+ --motion-duration-normal:240ms;
+ --motion-duration-slow:320ms;
+ --motion-ease-brand-out:cubic-bezier(0.16,1,0.3,1);
+ --motion-ease-standard:cubic-bezier(0.4,0,0.2,1);
+ --motion-ease-exit:cubic-bezier(0.4,0,1,1);
+ --motion-stagger-step:60ms;
+}
+
+:root[data-theme="dark"]{
+ --color-primary:#126DFF;
+ --color-primary-text:#5C9BFF;
+ --color-primary-bg:rgba(62,139,255,.20);
+ --color-primary-light:rgba(62,139,255,.12);
+ --state-hover:rgba(255,255,255,.06);
+ --state-press:rgba(255,255,255,.10);
+ --state-on-fill-hover:rgba(0,0,0,.12);
+ --state-on-fill-press:rgba(0,0,0,.22);
+ --color-success:#22C79D;
+ --color-success-text:#3DDBB0;
+ --color-success-bg:rgba(34,199,157,.12);
+ --color-warning-text:#FFC26B;
+ --color-warning-bg:rgba(248,171,66,.12);
+ --color-error:#FF6B6B;
+ --color-error-text:#FF8A80;
+ --color-error-bg:rgba(255,107,107,.12);
+ --color-text:#E8ECF4;
+ --color-text-secondary:#B3BDCD;
+ --color-text-tertiary:#8792A4;
+ --color-text-placeholder:#8792A4;
+ --color-separator:#2B3442;
+ --color-control-border:#3A4556;
+ --color-fill-quiet:#1F2631;
+ --color-bg-base:#0F141B;
+ --color-bg-container:#161C25;
+ --color-bg-elevated:#1C2330;
+ --color-bg-layout:#12171F;
+ --color-bg-chat:#111720;
+ --material:rgba(28,35,48,.80);
+ --scrim:rgba(0,0,0,.56);
+ --color-tint-blue-bg:rgba(62,139,255,.20);
+ --color-tint-blue:#8FB9FF;
+ --color-tint-purple-bg:rgba(149,120,255,.18);
+ --color-tint-purple:#B9A5FF;
+ --color-tint-teal-bg:rgba(45,212,191,.18);
+ --color-tint-teal:#5EE7D5;
+ --color-tint-amber-bg:rgba(248,171,66,.18);
+ --color-tint-amber:#FFC97A;
+ --shadow-raised:0 1px 2px rgba(0,0,0,.40), 0 8px 24px rgba(0,0,0,.32);
+ --shadow-floating:0 2px 8px rgba(0,0,0,.45), 0 20px 48px rgba(0,0,0,.55);
+ color-scheme:dark;
+}
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.json b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.json
new file mode 100644
index 00000000..d925c2e3
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/hugagentos-tokens.v2.json
@@ -0,0 +1,223 @@
+{
+ "name": "HugAgentOS",
+ "version": "2.0.0",
+ "color": {
+ "light": {
+ "color-primary": "#126DFF",
+ "color-on-primary": "#FFFFFF",
+ "color-primary-text": "#0F62E6",
+ "color-primary-bg": "#DBE9FF",
+ "color-primary-light": "#EBF2FF",
+ "state-hover": "rgba(16,24,40,.04)",
+ "state-press": "rgba(16,24,40,.08)",
+ "state-on-fill-hover": "rgba(0,0,0,.08)",
+ "state-on-fill-press": "rgba(0,0,0,.16)",
+ "color-success": "#02B589",
+ "color-success-text": "#067A5E",
+ "color-success-bg": "#E6FFF9",
+ "color-warning": "#F8AB42",
+ "color-warning-text": "#B45309",
+ "color-warning-bg": "#FFF5E8",
+ "color-error": "#FC5D5D",
+ "color-error-text": "#D92D20",
+ "color-error-bg": "#FFF0F0",
+ "color-danger-fill": "#D92D20",
+ "color-text": "#101828",
+ "color-text-secondary": "#475467",
+ "color-text-tertiary": "#667085",
+ "color-text-placeholder": "#667085",
+ "color-separator": "#E3E8EF",
+ "color-control-border": "#C5CCD8",
+ "color-fill-quiet": "#F2F4F7",
+ "color-bg-base": "#FFFFFF",
+ "color-bg-container": "#FFFFFF",
+ "color-bg-elevated": "#FFFFFF",
+ "color-bg-layout": "#F5F7FB",
+ "color-bg-chat": "#FDFDFC",
+ "color-brand-spark": "#19D3A7",
+ "material": "rgba(255,255,255,.82)",
+ "scrim": "rgba(16,24,40,.32)",
+ "color-tint-blue-bg": "#DBEAFE",
+ "color-tint-blue": "#1E40AF",
+ "color-tint-purple-bg": "#EDE9FE",
+ "color-tint-purple": "#6D28D9",
+ "color-tint-teal-bg": "#CCFBF1",
+ "color-tint-teal": "#0F766E",
+ "color-tint-amber-bg": "#FEF3C7",
+ "color-tint-amber": "#B45309",
+ "shadow-raised": "0 1px 2px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.06)",
+ "shadow-floating": "0 2px 8px rgba(16,24,40,.06), 0 20px 48px rgba(16,24,40,.14)"
+ },
+ "dark": {
+ "color-primary": "#126DFF",
+ "color-on-primary": "#FFFFFF",
+ "color-primary-text": "#5C9BFF",
+ "color-primary-bg": "rgba(62,139,255,.20)",
+ "color-primary-light": "rgba(62,139,255,.12)",
+ "state-hover": "rgba(255,255,255,.06)",
+ "state-press": "rgba(255,255,255,.10)",
+ "state-on-fill-hover": "rgba(0,0,0,.12)",
+ "state-on-fill-press": "rgba(0,0,0,.22)",
+ "color-success": "#22C79D",
+ "color-success-text": "#3DDBB0",
+ "color-success-bg": "rgba(34,199,157,.12)",
+ "color-warning": "#F8AB42",
+ "color-warning-text": "#FFC26B",
+ "color-warning-bg": "rgba(248,171,66,.12)",
+ "color-error": "#FF6B6B",
+ "color-error-text": "#FF8A80",
+ "color-error-bg": "rgba(255,107,107,.12)",
+ "color-danger-fill": "#D92D20",
+ "color-text": "#E8ECF4",
+ "color-text-secondary": "#B3BDCD",
+ "color-text-tertiary": "#8792A4",
+ "color-text-placeholder": "#8792A4",
+ "color-separator": "#2B3442",
+ "color-control-border": "#3A4556",
+ "color-fill-quiet": "#1F2631",
+ "color-bg-base": "#0F141B",
+ "color-bg-container": "#161C25",
+ "color-bg-elevated": "#1C2330",
+ "color-bg-layout": "#12171F",
+ "color-bg-chat": "#111720",
+ "color-brand-spark": "#19D3A7",
+ "material": "rgba(28,35,48,.80)",
+ "scrim": "rgba(0,0,0,.56)",
+ "color-tint-blue-bg": "rgba(62,139,255,.20)",
+ "color-tint-blue": "#8FB9FF",
+ "color-tint-purple-bg": "rgba(149,120,255,.18)",
+ "color-tint-purple": "#B9A5FF",
+ "color-tint-teal-bg": "rgba(45,212,191,.18)",
+ "color-tint-teal": "#5EE7D5",
+ "color-tint-amber-bg": "rgba(248,171,66,.18)",
+ "color-tint-amber": "#FFC97A",
+ "shadow-raised": "0 1px 2px rgba(0,0,0,.40), 0 8px 24px rgba(0,0,0,.32)",
+ "shadow-floating": "0 2px 8px rgba(0,0,0,.45), 0 20px 48px rgba(0,0,0,.55)"
+ }
+ },
+ "space": {
+ "space-1": 4,
+ "space-2": 8,
+ "space-3": 12,
+ "space-4": 16,
+ "space-6": 24,
+ "space-8": 32,
+ "space-12": 48,
+ "space-16": 64
+ },
+ "radius": {
+ "radius-xs": 6,
+ "radius-sm": 10,
+ "radius-md": 16,
+ "radius-lg": 24,
+ "radius-full": 999
+ },
+ "type": {
+ "display": {
+ "fontSize": 40,
+ "lineHeight": 48,
+ "fontWeight": 600,
+ "letterSpacing": "-0.02em"
+ },
+ "title-1": {
+ "fontSize": 28,
+ "lineHeight": 36,
+ "fontWeight": 600,
+ "letterSpacing": "-0.01em"
+ },
+ "title-2": {
+ "fontSize": 22,
+ "lineHeight": 30,
+ "fontWeight": 600,
+ "letterSpacing": "0"
+ },
+ "title-3": {
+ "fontSize": 18,
+ "lineHeight": 26,
+ "fontWeight": 600,
+ "letterSpacing": "0"
+ },
+ "headline": {
+ "fontSize": 15,
+ "lineHeight": 22,
+ "fontWeight": 600,
+ "letterSpacing": "0"
+ },
+ "body-lg": {
+ "fontSize": 16,
+ "lineHeight": 28,
+ "fontWeight": 400,
+ "letterSpacing": "0"
+ },
+ "body": {
+ "fontSize": 14,
+ "lineHeight": 22,
+ "fontWeight": 400,
+ "letterSpacing": "0"
+ },
+ "footnote": {
+ "fontSize": 13,
+ "lineHeight": 20,
+ "fontWeight": 400,
+ "letterSpacing": "0"
+ },
+ "caption": {
+ "fontSize": 12,
+ "lineHeight": 18,
+ "fontWeight": 400,
+ "letterSpacing": "0"
+ }
+ },
+ "font": {
+ "ui": "PingFang SC / Microsoft YaHei UI / Noto Sans CJK SC (system)",
+ "number": "DingTalk Sans",
+ "mono": "ui-monospace, SF Mono, Menlo, Consolas",
+ "marketing": "Source Han Sans SC / Noto Sans SC"
+ },
+ "motion": {
+ "duration": {
+ "instant": 100,
+ "fast": 160,
+ "normal": 240,
+ "slow": 320
+ },
+ "easing": {
+ "brand-out": [
+ 0.16,
+ 1,
+ 0.3,
+ 1
+ ],
+ "standard": [
+ 0.4,
+ 0,
+ 0.2,
+ 1
+ ],
+ "exit": [
+ 0.4,
+ 0,
+ 1,
+ 1
+ ]
+ },
+ "spring": {
+ "smooth": {
+ "visualDuration": 0.35,
+ "bounce": 0,
+ "stiffness": 322,
+ "damping": 36
+ },
+ "snappy": {
+ "visualDuration": 0.25,
+ "bounce": 0.15,
+ "stiffness": 632,
+ "damping": 43
+ }
+ },
+ "staggerStep": 60,
+ "staggerCap": 8,
+ "layoutAnimMaxItems": 20,
+ "freshEnterWindowMs": 2000
+ }
+}
diff --git a/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/motionTokens.v2.ts b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/motionTokens.v2.ts
new file mode 100644
index 00000000..da835955
--- /dev/null
+++ b/.claude/skills/hugagent-frontend-dev/assets/design-v2/tokens/motionTokens.v2.ts
@@ -0,0 +1,32 @@
+// HugAgentOS motion tokens v2 — drop-in superset of src/frontend/src/utils/motionTokens.ts.
+// New: SPRING.smooth / SPRING.snappy described as visualDuration + bounce (Motion >= 11 accepts both forms).
+// Durations / easings are unchanged; keep them in sync with styles/variables.css (--motion-*).
+
+export const DUR = { instant: 0.1, fast: 0.16, normal: 0.24, slow: 0.32 } as const;
+
+export const EASE = {
+ /** Enter / expand / rise */
+ brandOut: [0.16, 1, 0.3, 1] as const,
+ /** Two-way state changes (colour, opacity) */
+ standard: [0.4, 0, 0.2, 1] as const,
+ /** Exit: accelerate away, ~0.7x the enter duration */
+ exit: [0.4, 0, 1, 1] as const,
+};
+
+export const SPRING = {
+ /** Panels, drawers, collapse/expand, layout changes. No overshoot. (≈ stiffness 322, damping 36) */
+ smooth: { type: 'spring', visualDuration: 0.35, bounce: 0 },
+ /** Tab ink, toggles, badge confirmation. Slight overshoot. (≈ stiffness 632, damping 43) */
+ snappy: { type: 'spring', visualDuration: 0.25, bounce: 0.15 },
+ /** @deprecated use SPRING.snappy — 33% overshoot */
+ pop: { type: 'spring', stiffness: 500, damping: 30 },
+ /** @deprecated use SPRING.snappy */
+ ink: { type: 'spring', stiffness: 480, damping: 38 },
+ /** @deprecated use SPRING.smooth — 33% overshoot makes panels wobble */
+ panel: { type: 'spring', stiffness: 320, damping: 24 },
+} as const;
+
+export const STAGGER_STEP = 0.06;
+export const STAGGER_CAP = 8;
+export const LAYOUT_ANIM_MAX_ITEMS = 20;
+export const FRESH_ENTER_WINDOW_MS = 2000;
diff --git a/.claude/skills/hugagent-frontend-dev/references/css-variables.md b/.claude/skills/hugagent-frontend-dev/references/css-variables.md
index afe227db..4fa848a1 100644
--- a/.claude/skills/hugagent-frontend-dev/references/css-variables.md
+++ b/.claude/skills/hugagent-frontend-dev/references/css-variables.md
@@ -1,153 +1,174 @@
-# CSS 变量与样式规范参考
+# CSS 变量、命名与样式模式
-> **重要**: 颜色、圆角、间距等视觉规范以 `references/ui-design-spec.md` 为权威来源。本文件侧重 CSS 实践模式。
+真源:`src/frontend/src/styles/variables.css`(`:root` 浅色 + 文件末尾 `:root[data-theme="dark"]` 深色覆盖 +
+`:root[data-font-platform=…]` 字体优先级)。TS 镜像:`utils/motionTokens.ts`(动效)、`appTheme.ts`(antd token)。
+三处同步改。设计规则见 `ui-design-spec.md`(v2)。
-## 全局变量 (variables.css)
+---
-以下变量需与 UI 设计规范对齐(参见 `ui-design-spec.md` 第 12 节的完整映射)。
+## 1. v1 → v2 令牌对照(写新代码先查这张表)
-```css
-:root {
- /* 主色 */
- --color-primary: #126DFF;
- --color-primary-hover: #3C87FF;
- --color-primary-active: #0862F3;
- --color-primary-disabled: #A0C5FF;
- --color-primary-bg: #DBE9FF;
- --color-primary-light: #EBF2FF;
-
- /* 边框与填充 */
- --color-fill-heavy: #8C94A2;
- --color-fill-deep: #B3BAC8;
- --color-fill: #D8DBE2;
- --color-border: #E3E6EA;
- --color-fill-hover: #EBEDEE;
- --color-bg-gray: #F5F6F7;
-
- /* 文字 */
- --color-text: #101828;
- --color-text-secondary: #475467;
- --color-text-tertiary: #667085;
- --color-text-placeholder: #98A2B3;
- --color-text-white: #FFFFFF;
-
- /* 语义色 */
- --color-success: #02B589;
- --color-warning: #F8AB42;
- --color-error: #FC5D5D;
-
- /* 圆角 */
- --radius-xs: 6px; /* 标签等小面积 */
- --radius-sm: 10px; /* 按钮、输入框、下拉框 */
- --radius-md: 16px; /* 卡片视图、背景卡片 */
- --radius-lg: 24px; /* 弹窗、大输入框 */
-
- /* 间距 (4px 基准) */
- --space-xs: 4px;
- --space-sm: 8px;
- --space-md: 16px;
- --space-lg: 20px;
- --space-xl: 40px;
-
- /* 字体(后一条定义生效:系统栈优先,中文回退 PingFang/雅黑) */
- --font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
- "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
- --font-family-number: "DingTalk Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
-
- /* Motion 令牌(TS 侧镜像在 utils/motionTokens.ts) */
- --motion-duration-instant: 100ms;
- --motion-duration-fast: 160ms;
- --motion-duration-normal: 240ms;
- --motion-duration-slow: 320ms;
- /* easing: --motion-ease-brand-out cubic-bezier(.16,1,.3,1) / --motion-ease-standard cubic-bezier(.4,0,.2,1) */
-}
-```
+v2 令牌**尚未全部合入** `variables.css`。写代码前先 `grep -n -- '--令牌名' src/frontend/src/styles/variables.css` 确认:
-> 阴影令牌:`--shadow-card` / `--shadow-card-hover` / `--shadow-dropdown`(双层 ambient + key-light)。
-> 底部还有 legacy 别名(`--primary` / `--bg` / `--text` / `--muted` / `--border` 等),新代码不要使用。
+- **已存在** → 直接用;
+- **不存在、而你的改动需要它** → 按迁移清单第 1 项把它加进 `variables.css`(浅色 `:root` 和深色覆盖块**两处都加**,
+ 值取自 `assets/design-v2/tokens/hugagentos-tokens.v2.css`),同步 `appTheme.ts`(如涉及 antd),然后再用。
+ **不要**写死色值、**不要**引用未定义的变量名、**不要**写 `var(--x, #fff)` 兜底(深色门禁会拦)。
-## CSS 类名前缀
+| 用途 | v1(现行代码) | v2(目标) | 说明 |
+|---|---|---|---|
+| 强调填充 | `--color-primary` #126DFF(深色 #3E8BFF) | `--color-primary`(深色也 #126DFF) | 深色值待迁移 #6 |
+| 强调文字 / 链接 | `--color-primary` | `--color-primary-text` #0F62E6 / 深 #5C9BFF | 新增 |
+| 主色上的文字 | `--color-text-white` | `--color-on-primary` | 新增 |
+| 悬停 / 按下 | `--color-*-hover` / `--color-*-active` 色阶 | `--state-hover` / `--state-press` / `--state-on-fill-hover` / `--state-on-fill-press` 叠加层 | **新代码禁用色阶** |
+| 禁用 | `--color-*-disabled` | `opacity: .4` | |
+| 成功/警告/错误 文字 | `--color-success` 等直接当文字 | `--color-success-text` / `--color-warning-text` / `--color-error-text` | P0 对比度 |
+| 成功/警告/错误 浅底 | `--color-*-light`(v1 的 `*-bg` 是更深一档) | `--color-success-bg` #E6FFF9 等 | **注意:v2 的 `*-bg` 取 v1 `*-light` 的值** |
+| 破坏性实心按钮 | — | `--color-danger-fill` #D92D20 | 新增 |
+| 占位符 | `--color-text-placeholder` #98A2B3 | 同名,值改为 #667085(= 三级文字) | 迁移 #7 |
+| 分隔线 | `--color-border` #E3E8EF | `--color-separator` | 改名,值相同 |
+| 输入框边框 | `--color-border` / `--color-fill` | `--color-control-border` #C5CCD8 | 新增 |
+| 次要按钮 / 胶囊 / 用户气泡 | `--color-bg-gray` / `--color-fill-hover` | `--color-fill-quiet` #F2F4F7 | 新增 |
+| 浮层材质 | 各处自写半透明 | `--material` + `backdrop-filter: blur(20px)` | 新增 |
+| 模态遮罩 | antd 默认 | `--scrim` | 新增 |
+| 静止卡片投影 | `--shadow-card` | **无投影** | 迁移 #9 |
+| 悬停卡片 / 对话输入区 | `--shadow-card-hover` / `--shadow-card` | `--shadow-raised` | 调整 |
+| 菜单 / 弹窗 | `--shadow-dropdown` | `--shadow-floating` | 调整 |
+| 分类色 | tint × 7(purple/cyan/teal/amber/green/red/blue) | tint × 4(blue/purple/teal/amber) | cyan/green/red 不新增引用 |
+| 胶囊圆角 | 写 `999px` | `--radius-full` | 新增 |
+| 间距 | `--space-xs/sm/md/lg/xl` = 4/8/16/**20**/**40** | `--space-1/2/3/4/6/8/12/16` = 4/8/12/16/24/32/48/64 | 20/40 不在栅格上 |
+| 字号 | `--font-size-xs/sm/md/lg/xl/xxl` = 12/14/16/18/22/44(px) | 九级角色:display / title-1/2/3 / headline / body-lg / body / footnote / caption(rem) | 见 ui-design-spec §2 |
+| 品牌火花绿 | — | `--color-brand-spark` | 仅标志,界面禁用 |
+| 弹簧 | `SPRING.pop / ink / panel` | `SPRING.smooth / snappy` | 迁移 #2 |
-所有自定义类使用 `.jx-` 前缀,避免与 Ant Design 冲突。
+在 v2 间距令牌合入前:4/8/16 用 `--space-xs/sm/md`,12/24/32/48/64 可直接写 px(门禁只管颜色),
+**不要**在新代码里用 `--space-lg`(20) / `--space-xl`(40)。
-## 命名规则 (BEM-like)
+---
-| 类型 | 格式 | 示例 |
-|------|------|------|
-| Block | `.jx-blockName` | `.jx-chatArea` |
-| Element | `.jx-blockName-element` | `.jx-chatArea-header` |
-| Modifier | `.jx-blockName--modifier` | `.jx-chatArea--empty` |
-| Sub-element | `.jx-blockName-element-sub` | `.jx-fileCard-icon-bg` |
+## 2. 现行令牌速查(variables.css 当前值)
-## 常用尺寸
+### 背景四级 + 对话底
-| 用途 | 值 |
-|------|-----|
-| 侧边栏宽度 | 260px (展开) / 0 (收起) |
-| 输入框最大宽度 | 840px |
-| 消息最大宽度 | 840px |
-| 面板内边距 | 20px (桌面) / 16px (移动) |
-| 卡片间距 | 8px - 16px |
-| 卡片圆角 | 16px (`--radius-md`) |
-| 输入框圆角 | 10px (`--radius-sm`) |
-| 按钮圆角 | 10px (`--radius-sm`) |
-| 弹窗圆角 | 24px (`--radius-lg`) |
-| 按钮最小高度 | 36px |
+| 令牌 | 浅色 | 深色 |
+|---|---|---|
+| `--color-bg-base` | #FFFFFF | #0F141B |
+| `--color-bg-container` | #FFFFFF | #161C25 |
+| `--color-bg-elevated` | #FFFFFF | #1C2330 |
+| `--color-bg-layout` | #F5F7FB | #12171F |
+| `--color-bg-chat` | #FDFDFC | #111720 |
-## 常用样式模式
+### 文字
-### 卡片
+`--color-text` #101828 · `--color-text-secondary` #475467 · `--color-text-tertiary` #667085 ·
+`--color-text-placeholder` #98A2B3(v2 改 #667085)· `--color-text-white` #FFFFFF
-```css
-.jx-card {
- padding: var(--space-md) var(--space-lg); /* 16px 20px */
- border-radius: var(--radius-md); /* 16px */
- border: 1px solid var(--color-border);
- background: #fff;
- transition: box-shadow 0.2s, border-color 0.2s;
-}
-.jx-card:hover {
- border-color: var(--color-primary-hover);
- box-shadow: 0 4px 12px rgba(18, 109, 255, 0.08);
-}
-```
+### 主色与语义
+
+- 主色:`--color-primary` #126DFF、`-hover` #3C87FF、`-active` #0862F3、`-disabled` #A0C5FF、`-bg` #DBE9FF、`-light` #EBF2FF
+- 语义:`--color-{success|warning|error}` + `-hover/-active/-disabled/-bg/-light`(v1 六档,新代码按上表换 v2)
+- 线与填充:`--color-border` #E3E8EF、`--color-fill` #DDE3EC、`--color-fill-hover` #EEF1F5、`--color-bg-gray` #F4F6F9、
+ `--color-fill-deep` #98A2B3、`--color-fill-heavy` #8C94A2
+- 分类 tint:`--color-tint-{purple,cyan,teal,amber,green,red,blue}-bg` + `--color-tint-{…}`(深浅两档已定义)
+
+### 圆角 / 间距 / 字号 / 字体
+
+- `--radius-xs/sm/md/lg` = 6 / 10 / 16 / 24
+- `--space-xs/sm/md/lg/xl` = 4 / 8 / 16 / 20 / 40
+- `--font-size-xs/sm/md/lg/xl/xxl` = 12 / 14 / 16 / 18 / 22 / 44 px
+- `--font-family`(`--font-family-cjk` 按 `data-font-platform` 切换 PingFang SC / Microsoft YaHei UI / Noto Sans CJK SC)、
+ `--font-family-mono`、`--font-family-number`(DingTalk Sans)
+
+### 投影
+
+`--shadow-card` / `--shadow-card-hover` / `--shadow-dropdown`(深浅两档)
+
+### 动效
+
+`--motion-duration-instant/fast/normal/slow` = 100 / 160 / 240 / 320ms ·
+`--motion-ease-brand-out` / `--motion-ease-standard` / `--motion-ease-exit` · `--motion-stagger-step` 60ms
-### 按钮
+### 局部域令牌(只在对应模块内用)
+
+- `--delivery-*`:交付文件卡片(文件类型图标色等)
+- `--color-personal-*` / `--shadow-personal-composer`:Personal Agent 对话皮肤
+- `--module-*`:模块导航轨与二级边栏
+- `--scrollbar-*`:滚动条
+- `--bg / --text / --muted / --border / --primary / --primary2 / --accent / --shadow1`:**旧别名,新代码禁用**
+
+---
+
+## 3. 命名规则
```css
-.jx-btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 8px 18px;
- border-radius: var(--radius-sm); /* 10px */
- font-size: 14px;
- cursor: pointer;
- transition: all 0.2s;
- min-height: 36px;
-}
+.jx-chatArea { } /* Block:.jx- 前缀 + camelCase */
+.jx-chatArea-header { } /* Element:连字符 */
+.jx-chatArea--empty { } /* Modifier:双连字符 */
```
-### 输入区
+- 所有自定义类 `.jx-` 前缀;关键帧 `jx-kf-名称`,动效工具类 `jx-anim-名称`
+- 组件私有的自定义属性用 `--jx--`,**值也必须引令牌**(`css-var-literal-color` 规则)
+- 样式按功能放 `styles/` 对应文件或子目录(`styles/chat/`、`styles/catalog/`、`styles/myspace/`、`styles/automation/`、
+ `styles/mobile/`);`styles/index.ts` 统一 import,组件专属样式可在组件内直接 import
+
+---
+
+## 4. 常用样式模式(v2 写法)
```css
-.jx-inputArea {
- width: 100%;
- max-width: 840px;
- margin: 0 auto;
- border-radius: var(--radius-lg); /* 24px */
- border: 1.5px solid var(--color-border);
- background: #fff;
+/* L1 卡片:静止无投影,悬停浮起 */
+.jx-xxx-card {
+ background: var(--color-bg-container);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+ transition: transform var(--motion-duration-fast) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-fast) var(--motion-ease-standard);
+}
+.jx-xxx-card:hover { transform: translateY(-1px); box-shadow: var(--shadow-raised); }
+
+/* 中性控件悬停:叠加层而非换色 */
+.jx-xxx-item:hover { background: var(--state-hover); }
+.jx-xxx-item:active { background: var(--state-press); }
+
+/* L2 浮层:材质 + 模糊,从触发点展开 */
+.jx-xxx-menu {
+ background: var(--material);
+ backdrop-filter: blur(20px);
+ border-radius: var(--radius-md); /* 外 16 */
+ padding: 6px; /* 内边距 6 */
+ box-shadow: var(--shadow-floating);
+ transform-origin: var(--jx-menu-origin, top left);
}
+.jx-xxx-menu-item { border-radius: var(--radius-sm); } /* 内 10 = 16 − 6,同心 */
+
+/* 语义提示条:浅底 + 前景文字成对 */
+.jx-xxx-hint--warning { background: var(--color-warning-bg); color: var(--color-warning-text); }
+
+/* 分类标签:tint 成对 */
+.jx-xxx-tag--skill { background: var(--color-tint-purple-bg); color: var(--color-tint-purple); border-radius: var(--radius-full); }
+
+/* 半透明面 / 墨色浮层(深色下自动正确) */
+.jx-xxx-glass { background: color-mix(in srgb, var(--color-bg-container) 60%, transparent); }
+.jx-xxx-ink { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
+
+/* 数字列 */
+.jx-xxx-num { font-family: var(--font-family-number); font-variant-numeric: tabular-nums; }
+
+/* 小图标按钮:视觉 28,点击区外扩到 32 */
+.jx-xxx-iconBtn { position: relative; width: 28px; height: 28px; border-radius: var(--radius-full); }
+.jx-xxx-iconBtn::after { content: ''; position: absolute; inset: -2px; }
```
-## 文件索引
+---
+
+## 5. 布局约定(模块页面与容器响应式)
-| 文件 | 内容 |
-|------|------|
-| `variables.css` | CSS 变量 + body 背景 + AntD 覆写 |
-| `chat.css` | 聊天区域、输入框、消息气泡、空状态 |
-| `sidebar.css` | 侧边栏、导航、历史列表 |
-| `catalog.css` | Catalog 面板、搜索、列表项 |
-| `tool.css` | 工具输出面板、展开/折叠 |
-| `common.css` | 全局工具类、弹窗覆写 |
+- **模块主页面**(项目、我的空间、站点、应用中心、能力中心、定时任务、设置、更新记录、实验室):公共内容区是全宽滚动视口,
+ 几何集中在 `styles/workspace-layout.css`。模块面板用正常文档流,**不要**再给面板自己设 `height:100%` + `overflow:auto`
+ 造第二个滚动容器,也不要用 `overflow-x:hidden`(会隐式把 `overflow-y` 变成 auto)。限宽只给模块内容。
+ 回归:`npm run test:module-pages`。
+- **容器响应式**:聊天工具栏、项目 / 能力目录 / 知识库卡片按所在面板的可用宽度排版——容器提供宽度,后代用容器查询响应;
+ 不要让元素查询自身,不要用 JS 重复维护 CSS 断点。入口 `components/chat/ComposerToolbar.tsx`、
+ `styles/chat/toolbar-layout.css`、`styles/projects.css`、`styles/catalog/`。
+ 回归:`npm run test:container-layout-browser`、`npm run test:chat-width-browser`。
+- 聊天、Canvas、插件工作区和真正的编辑器 / 限高预览保留专用布局。
diff --git a/.claude/skills/hugagent-frontend-dev/references/icon-assets.md b/.claude/skills/hugagent-frontend-dev/references/icon-assets.md
index c781434b..f0ae7e10 100644
--- a/.claude/skills/hugagent-frontend-dev/references/icon-assets.md
+++ b/.claude/skills/hugagent-frontend-dev/references/icon-assets.md
@@ -1,7 +1,38 @@
# 图标资产清单
-本技能内置了项目 UI 设计稿中的全部 SVG 图标,位于 `assets/` 目录。
-当需要在前端使用图标时,优先从此清单中选取,保持视觉一致性。
+本技能带两批图标:
+
+- `assets/design-v2/`:**v2 设计规范**的界面图标风格样例、标志与网页图标(新代码以此为风格基准)
+- `assets/icons/`、`assets/mcp-icons/`:v1 设计稿导出的图标(存量界面在用,部分带硬编码色值,复用前先改成 `currentColor`)
+
+## v2 资产(`assets/design-v2/`)
+
+| 目录 | 内容 | 规则 |
+|---|---|---|
+| `icons-ui/` | 12 个界面图标:new-chat / search / knowledge / skill / automation / my-space / attachment / settings / send / chevron-right / check / close | 24px 画板、留白 2px、线宽 1.5、圆头圆角、`stroke="currentColor"` |
+| `logo/hugagentos-mark*.svg` | 方形标:标准 / small(≤24px 用)/ reverse(深色底)/ mono-black / mono-white | 只用于方形容器;禁止与标准字并排 |
+| `logo/hugagentos-glyph-*.svg` | 环线(无底板):蓝 / 白 / 黑 | 头像、App 图标内自行合成 |
+| `logo/favicon.svg` | 网页图标(小尺寸版) | 16/32px 使用 |
+| `tokens/` | v2 令牌参考实现(CSS / JSON / motionTokens / appTheme) | 见 `css-variables.md` |
+
+图标规则(v2,详见 `ui-design-spec.md` §6):图标是文字的一部分——尺寸跟随字号、线宽跟随字重(13px→1.5,15/20px→2)、
+颜色继承文字色;默认线性,选中态换面性。**不要给图标单独上品牌色,火花绿 `#19D3A7` 不进界面。**
+
+```tsx
+// v2 写法:currentColor + 跟随字号
+export function SearchIcon({ size = '1em' }: { size?: string | number }) {
+ return (
+
+
+
+ );
+}
+```
+
+---
+
+## v1 图标
## 使用方式
@@ -89,10 +120,9 @@ import sendIcon from '../assets/icons/send.svg';
---
-## 设计约束
+## 设计约束(v2 替代 v1 的约定)
-- **主色**: 图标主色为 `#126DFF`(蓝色),与设计规范一致
-- **灰色**: 非激活态图标用 `#808080`
-- **尺寸**: 常用 16px / 28px / 32px / 34px / 38px(均为 4 的倍数或接近)
-- **风格**: 线性为主,部分填充,圆角端点
-- 详见 `ui-design-spec.md` 第 3 节「图标」规范
+- v1 约定"图标主色 `#126DFF`、非激活 `#808080`"**已废止**:v2 图标继承文字色,激活态用面性而不是换色
+- v1 SVG 里的硬编码 `fill` / `stroke` 色值在深色模式下不会反相;复用前改成 `currentColor`,否则过不了视觉走查
+- 尺寸跟随字号(13 / 15 / 20px 常用),点击区按桌面 ≥32、触屏 ≥44 外扩
+- 详见 `ui-design-spec.md` §6「图标」
diff --git a/.claude/skills/hugagent-frontend-dev/references/sse-events.md b/.claude/skills/hugagent-frontend-dev/references/sse-events.md
index 8764623b..4b31de68 100644
--- a/.claude/skills/hugagent-frontend-dev/references/sse-events.md
+++ b/.claude/skills/hugagent-frontend-dev/references/sse-events.md
@@ -1,246 +1,126 @@
# SSE 事件类型参考
-统一回复端点 `POST /v1/agents/responses` 在请求体 `stream: true` 时返回 Server-Sent Events(默认 false 返回完整 JSON)。后端把 run 跑在后台
-(ChatRun + Redis Stream),SSE 只是"跟随"——断线后可用 `GET /v1/chats/stream/{run_id}`
-重新接上同一条流续播。
+统一回复端点 `POST /v1/agents/responses`(`stream: true` 返回 SSE,默认返回完整 JSON)。后端把每一轮跑成后台 run
+(`core/runs` 持有租约、事件日志与续播;`core/chat/runtime/worker.py` 消费事件并持久化输出),SSE 只是"跟随"。
-**前端解析单一真源是 `hooks/chatStream.ts`(`processChatStream()`)**;`hooks/useStreaming.ts`
-只做发送/中止/续播/排队编排;通用分帧器在 `utils/sse.ts`(`parseSSE`)。后端事件产出主体在
-`orchestration/streaming.py`(StreamingAgent)与 `orchestration/chat_run_executor.py`。
+| 端 | 位置 |
+|---|---|
+| 前端解析单一真源 | `hooks/chatStream.ts` → `hooks/chatStream/`(`consume.ts` 入口;`payload.ts` 分帧与分派;`lifecycleAndTools.ts` / `toolResultsAndMeta.ts` / `controls.ts` 分领域 reducer;`snapshot*.ts` 快照恢复) |
+| 前端发送 / 续播编排 | `hooks/useStreaming.ts` + `hooks/streamSend.ts` / `streamFollowing.ts` / `streamRecovery.ts` / `streamQueue.ts` |
+| 通用分帧器 | `utils/sse.ts`(`parseSSE`) |
+| 订阅客户端 | `runSubscriptionClient.ts`(`GET /v1/chats/stream/{run_id}/subscription`) |
+| 后端事件产出 | `core/agent_runtime/stream_main.py` / `stream_direct.py`、`core/llm/runtime/stream_events.py`;SSE 输出 `api/transports/run_sse.py` |
-## 流格式
-
-```
-data: {"type": "", ...payload} # JSON 事件
-data: [DONE] # 流终止标记
-: heartbeat # SSE 注释行心跳(直接忽略)
-```
-
-> 没有独立的 `done` 事件——结束是 `end` 事件或 `[DONE]`。正文事件规范名是 `content`,
-> 但 `ai_message` / `text` / `delta` 都会被当作正文别名接受。
-
-## 事件类型
-
-### `run_started` — 首帧
-
-```json
-{"type": "run_started", "run_id": "run_xxx", "message_id": "msg_xxx"}
-```
-
-- 必须记录到 `useChatStore.setActiveRun(chatId, { runId, messageId })`
-- `run_id` 是断线续播的凭据
-
-### `content` — 文本增量
-
-```json
-{"type": "content", "delta": "你好"}
-```
-
-- 取 `delta || content || text` 追加到当前消息内容
-- 兼容别名:`ai_message` / `text` / `delta`
-- 内嵌 `... ` 标签的模型由 parseBuffer 解析为 thinking(structuredReasoning=false 时)
-
-### `thinking` — 思考过程
-
-```json
-{"type": "thinking", "delta": "让我分析一下..."}
-```
-
-- 取 `content || text || delta`;有 `delta` 字段时按增量追加到最近的 thinking segment
-- 收到带 `delta` 的 thinking 即标记 structuredReasoning(思考走独立通道,如 reasoning_content),关闭 `` 内嵌解析
-- 兼容别名:`thought`
-
-### `content_replace` — 整段替换正文
-
-```json
-{"type": "content_replace", "content": "重写后的完整正文"}
-```
-
-- 本体治理评审后重写答案时使用:**整体替换**当前消息正文,而不是追加
-
-### `tool_call_start` / `tool_call_delta` — 工具参数流式展示
-
-```json
-{"type": "tool_call_start", "id": "call_abc123", "name": "internet_search"}
-{"type": "tool_call_delta", "id": "call_abc123", "delta": "{\"query\": \"北京"}
-```
-
-- 参数尚未生成完时先出卡片(status='running'),delta 增量拼接参数 JSON 实时展示
-- 随后的 `tool_call` 带完整参数,按 id 更新同一张卡片
-
-### `tool_call` — 工具调用(完整参数)
-
-```json
-{"type": "tool_call", "id": "call_abc123", "name": "internet_search", "input": {"query": "北京 天气"}}
-```
-
-- 参数字段取 `input ?? args ?? tool_args ?? arguments`
-- 按工具 id 去重:已存在则更新(status='running'),否则 push 到 `toolCalls[]` 并追加 `{ type: 'tool', toolIndex }` segment
-- 兼容别名:`tool_use` / `tool_start`
-
-### `tool_result` — 工具执行结果
-
-```json
-{
- "type": "tool_result",
- "id": "call_abc123",
- "output": "搜索结果内容...",
- "citations": [{"title": "...", "url": "...", "source": "internet_search"}]
-}
-```
+**新增事件处理只写在 `hooks/chatStream/` 对应的 reducer 里**,不要在别处再解析 SSE。
-- 结果字段取 `output ?? result`;`error` 字段存在 → status='error',否则 'success'
-- `citations` 追加到 `allCitations[]`
-- `subagent_name` 存在时改写 displayName 为「调用子智能体:xxx」
-- 兼容别名:`tool_end`
-
-### `tool_pending` — 工具等待批复
+## 流格式
-```json
-{"type": "tool_pending"}
```
-
-- UI 进入 pending 态;收到下一个非 heartbeat 事件自动解除
-
-### `batch_confirm` — 批量计划确认
-
-```json
-{"type": "batch_confirm", "plan_id": "...", "total": 20, "source_type": "xlsx",
- "preview": [...], "default_template": "...", "placeholder_keys": [...], "chat_id": "...", "warnings": [...]}
+data: {"type": "", "server_ts": 1730000000000, "event_offset": 42, ...payload}
+data: [DONE] # 流终止
+: heartbeat # 注释行心跳,忽略
```
-- batch_runner MCP 返回了执行计划,后端已暂停 agent
-- 调 `useBatchStore.setPendingConfirm(...)` 打开确认弹窗,用户审阅/编辑模板后才执行
+- `server_ts` 更新最后活动时间(停滞检测);`event_offset` 记录续播偏移。
+- 无 `type` 的裸文本帧当作正文追加。
+- 正文规范名 `content`,`ai_message` / `text` / `delta` 均作别名接受。
-### `file_confirm` — 我的空间写确认
+## 订阅帧(续播 / 刷新后)
-```json
-{"type": "file_confirm", "confirm_id": "...", ...}
-{"type": "file_confirm", "confirm_id": "...", "expired": true}
-```
+| 事件 | 说明 |
+|---|---|
+| `run_snapshot` | 订阅首帧,携带 `state`:对齐的原生快照(已产生的正文/思考/工具段),`installRunSnapshot()` 直接装入 |
+| `run_terminal` | run 已终态,结束跟随 |
-- 某工具协程已挂起等用户确认 /myspace 写。**本 SSE 流不结束**——用户点允许/拒绝走带外
- `POST /file-confirm`,挂起的工具原地续跑,后续 tool_result/meta 仍从同一条流来
-- 入队 `useUIStore.enqueuePendingConfirm(chatId, info)`;`expired: true` 表示该项超时回收,
- 调 `resolvePendingConfirm` 只摘掉这一个 confirm_id
+## 生命周期
-### `design_pick` — 建站方案三选一
+| 事件 | 载荷要点 | 处理 |
+|---|---|---|
+| `run_started` | `run_id`, `message_id` | 首帧,存 activeRun(续播凭据) |
+| `queued_run_started` | 排队消息交接信息 | 排队中的下一条消息开始执行(`parseQueuedRunHandoff`) |
+| `steer_applied` | 插话边界 | 运行中插话被采纳,切分正文边界 |
+| `meta` | `message_id`, `citations`, `workspace_files`, … | 设置 messageId;`citations` 非空时整体替换 |
+| `follow_up` | `follow_up_questions[]` | 追问建议;也可能流结束后后台生成写入消息 `extra_data` |
+| `error` | `error` | 终止本次流处理 |
+| `end` / `[DONE]` | — | 收尾所有 running 工具,标记流结束 |
-- 与 `file_confirm` 同机制:流不结束,用户选择走带外接口,工具协程原地续跑
+## 正文与思考
-### `plan_update` — 计划清单更新
+| 事件 | 处理 |
+|---|---|
+| `content`(别名 `ai_message` / `text` / `delta`) | 取 `delta || content || text` 追加;内嵌 `` 的模型由 parseBuffer 拆出思考 |
+| `thinking`(别名 `thought`) | 带 `delta` 时按增量追加到最近的 thinking 段,并标记 structuredReasoning |
+| `content_replace` | **整体替换**当前正文(本体治理重写答案) |
-```json
-{"type": "plan_update", "plan": [{"content": "...", "status": "completed"}, ...]}
-```
+## 工具
-- `update_plan` 工具产生的轻量计划清单;run 正常结束时后端自动补发全部 completed 的收尾帧
+| 事件 | 载荷要点 | 处理 |
+|---|---|---|
+| `tool_call_start`(兼容 `tool_use` / `tool_start`) | `id`, `name` | 先出运行中的工具行 |
+| `tool_call_delta` | `id`, `arguments_delta` | 参数 JSON 增量拼接实时展示 |
+| `tool_call` | `id`, `name`, `input`(兼容 `args` / `tool_args` / `arguments`) | 按 id 更新同一项 |
+| `tool_progress` | `id`, `progress`, `total`, `message` | 运行中工具的进度注记(`progressNote`,如"3/10") |
+| `tool_result`(兼容 `tool_end`) | `id`, `output`/`result`, `error`, `citations` | 置 success / error;`subagent_name` 存在时改写展示名 |
+| `tool_pending` | — | 工具等待批复,下一个非心跳事件自动解除 |
+| `subagent_event` | `subType`: tool_call / tool_call_delta / tool_result / thinking / content / error | 渲染为父工具的 `subSteps` |
+| `vision_progress` | `status`, `count` | 视觉桥在模型开口前读图,写 `chatStore.setVisionReading` |
-### `subagent_event` — 子智能体嵌套事件
+> **UI 规范(v2)**:工具调用默认是**一行安静的文字**(做了什么 · 结果多少 · 用了多久),点击展开;运行中用文字微光,
+> 完成用 `statusIn` 落定图标。只有文件、表格、网页等产物做成卡片。见 `ui-design-spec.md` §7.3 / §8.6。
-```json
-{"type": "subagent_event", "subType": "tool_call", ...}
-```
+## 需要用户介入(流不结束,带外批复后原地续跑)
-- `subType` 取值:`tool_call` / `tool_call_delta` / `tool_result` / `thinking` / `content` / `error`
-- 渲染为父工具卡片的 `subSteps` 子步骤
+| 事件 | 处理 |
+|---|---|
+| `file_confirm`(`confirm_id`;`expired: true` 表示超时回收) | 入队 `useUIStore.enqueuePendingConfirm`;用户允许/拒绝走带外接口 |
+| `design_pick` / `choose_design` 工具 | 建站三选一卡片,同 file_confirm 机制;超时由助手自行选择 |
+| `user_question`(`request_id`, `questions[]`) | `ask_user_question` 挂起 run,常驻输入区被问答面板替换;`enqueuePendingUserQuestion`,按 `request_id` 去重 |
+| `user_question_resolved`(`request_id`, `outcome`) | **后端拥有终态**:在这里摘除;`outcome === 'timeout'` 时提示采用默认方案 |
-### `steer_applied` — 运行中插话生效
+> v2:这些"等你确认"项是**唯一允许扩散涟漪**的状态。
-- 用户在 run 进行中追加的指令(steering)被后端采纳的边界标记
+## 上下文与压缩
-### `compaction_notice` — 上下文压缩提示
+| 事件 | 处理 |
+|---|---|
+| `context_usage` | 上下文用量快照 → `chatStore.setContextUsage`(ContextGauge) |
+| `compaction_progress` | 压缩进度(`phase` 如 `post_turn` = 本轮结束后执行的压缩)→ upsert `compaction` 段 |
+| `compaction_notice` | 上一轮后台压缩完成的提示 → `chatStore.setContextCompaction` |
-- 长会话触发上下文压缩时的提示帧
+## 计划 / 循环
-### `model_progress` — 模型活性信号
+- `plan_update`:`update_plan` 工具产生的轻量计划清单;run 正常结束时后端补发全部 completed 的收尾帧。
+- Plan 模式(`plan_step_*` / `plan_complete` 等)与自主循环(`loop_*`)事件经 `ChatStreamOptions.onEvent` 钩子交给
+ `usePlanMode` / `useLoopMode` 先处理,钩子返回 true 则不再进通用 reducer。
-- 表示"模型还在飞行中",用于喂 run 卡死看门狗/停滞检测,UI 不直接渲染
+## 本体治理
-### `ontology_*` — 本体治理事件族
+`ontology_activation` / `ontology_gate` / `ontology_review` / `ontology_repair` / `ontology_revision` /
+`ontology_revision_thinking`;`scope === 'ontology_revision'` 的工具事件单独分流(重写结果经 `content_replace` 落地)。
-`ontology_activation` / `ontology_gate` / `ontology_review` / `ontology_repair` /
-`ontology_revision` / `ontology_revision_thinking`——本体激活、工具闸、输出评审、修复与重写过程
-(重写结果经 `content_replace` 落地)。另有 `eventObj.scope === 'ontology_revision'` 的分流。
+## 已退役(不要再处理或产出)
-### `follow_up` — 追问建议
-
-```json
-{"type": "follow_up", "follow_up_questions": ["追问1", "追问2"]}
-```
-
-- 追问也可能不走 SSE:流结束后由后台任务生成,写进消息 `extra_data.follow_up_questions`,
- 前端轮询/刷新历史时取到
-
-### `meta` — 元信息
-
-```json
-{"type": "meta", "message_id": "msg_xxx", "citations": [...], "workspace_files": [...], "artifacts": [...]}
-```
-
-- 设置 `messageId`;`citations` 非空时**整体替换** allCitations
-- `artifacts` 追加为下载类 toolCall 展示
-
-### `error` — 错误
-
-```json
-{"type": "error", "error": "错误描述"}
-```
-
-- 直接 throw,终止本次流处理
-
-### `end` / `[DONE]` — 流结束
-
-- `{"type": "end"}` 或 `data: [DONE]` 均表示结束
-- finalize 所有 running 状态的 toolCalls,触发后续操作(摘要、分类等由后端负责)
+- `batch_confirm`(批量执行已退役)
+- `model_progress`(活性信号改由 `server_ts` / 心跳承担)
## Segment 渲染顺序
-消息通过 `segments[]` 按顺序渲染:
+`segments[]` 按到达顺序渲染:`thinking` / `tool` / `text` / `plan` / `compaction`。
-```
-[thinking] → [tool] → [thinking] → [tool] → [text]
-```
-
-每个 segment 类型对应不同的 UI 组件:
-- `thinking` → ThinkingBlock (可折叠)
-- `tool` → ToolCall (可展开,显示名称/参数/结果;子智能体带 subSteps)
-- `text` → Markdown 渲染 + 引用锚点胶囊(`[锚文本](cite:eN)`)
+- `thinking` → 一行"已思考 N 秒",可折叠
+- `tool` → 一行过程记录,可展开(子智能体带 subSteps)
+- `text` → Markdown + 引用锚点胶囊(`[锚文本](cite:eN)`)
- `plan` → 计划清单条
-
-## 中断流
-
-```typescript
-// AbortController 中断
-const abortController = new AbortController();
-
-// 发送请求时
-const r = await authFetch(url, {
- method: 'POST',
- signal: abortController.signal,
- body: ...,
-});
-
-// 用户点击停止
-abortController.abort();
-```
+- `compaction` → 压缩过程行
## 断线续播
-```typescript
-// run_started 时已存下 activeRun
-const { runId } = useChatStore.getState().activeRuns[chatId];
+`hooks/streamRecovery.ts`:
-// 重连:从后台 run 的 Redis Stream 重放 + 继续跟随
-const r = await authFetch(`${apiUrl}/v1/chats/stream/${runId}`);
-// 事件格式与 POST /v1/agents/responses 完全一致
-```
-
-实际编排在 `useStreaming.ts::resumeRunIfAny`:
-
-- `getActiveChatRun` 探测活跃 run;已终态则清僵尸状态(`cleanupZombieRunState`)
-- 普通对话从 `last_event_offset` **断点续传**;`plan_generate` / `plan_execute` / `autonomous_loop`
- 从 offset 0 **全量重放**(loop 会先截掉尾部 assistant 占位再重建)
-- 跨标签页互斥:Web Locks `hugagent_run_follow_${run_id}`(`ifAvailable: true`),
- 只允许一个标签页跟随同一 run
+- 探测活跃 run(`last_event_offset`);已终态则清理僵尸状态
+- 普通对话:`openRunSubscription(runId)` → `run_snapshot` 首帧恢复已产生内容,再继续跟随
+- `plan_generate` / `plan_execute` / `autonomous_loop`:从 offset 0 全量重放
+- 跨标签页互斥:Web Locks `hugagent_run_follow_${run_id}`,只允许一个标签页跟随同一 run
- 运行中插话:`steerChatRun` / `withdrawChatRunSteer`,失败时降级为下一轮普通消息
+
+中断:`AbortController.abort()` 只断开跟随;取消 run 走显式取消接口(`markRunCancelledByUser`)。
diff --git a/.claude/skills/hugagent-frontend-dev/references/ui-design-spec.md b/.claude/skills/hugagent-frontend-dev/references/ui-design-spec.md
index 8acca712..081f9362 100644
--- a/.claude/skills/hugagent-frontend-dev/references/ui-design-spec.md
+++ b/.claude/skills/hugagent-frontend-dev/references/ui-design-spec.md
@@ -1,448 +1,423 @@
-# UI 设计规范参考
+# HugAgentOS UI 设计规范 v2(开发落地版)
-本文档基于 UI 团队出品的设计规范(`img/设计规范/`),是前端开发的视觉对齐依据。
+> 来源:《HugAgentOS 设计规范 v2.0 · 2026.10》(`HugAgentOS-UI设计规范/HugAgentOS设计规范-v2.html` + `assets/`)。
+> v2 相对 v1 经过一次对抗性审查,修了 25 项问题(6 项 P0 可访问性/语义错误、14 项 P1 层级一致性、5 项 P2 品牌表达)。
+> 本文只保留写代码要用的规则与数值;品牌物料、宣传版式见原规范。
+>
+> **代码是唯一事实来源。** 改令牌时 `styles/variables.css`、`utils/motionTokens.ts`、`appTheme.ts` 三处同步改。
+> v2 令牌参考实现在本 skill 的 `assets/design-v2/tokens/`,**尚未全部合入仓库**——合入状态和落地方式见
+> `css-variables.md` 的「v1 → v2 对照」与本文末尾「迁移清单」。
---
-## 1. 颜色系统
-
-### 1.1 主色 (Primary Blue)
-
-| 状态 | 色值 | 用途 |
-|------|------|------|
-| 常规 | `#126DFF` | 主按钮、链接、选中态 |
-| 悬浮 | `#3C87FF` | hover 状态 |
-| 按下 | `#0862F3` | active/pressed 状态 |
-| 禁用、描边 | `#A0C5FF` | disabled 按钮描边 |
-| 背景 | `#DBE9FF` | 选中项背景、badge 背景 |
-| 浅色/白色悬浮 | `#EBF2FF` | 列表 hover、浅色背景 |
-
-### 1.2 边框与填充 (Neutral Gray)
-
-| 状态 | 色值 | 用途 |
-|------|------|------|
-| 重(填充或 icon) | `#8C94A2` | 实心 icon 填充 |
-| 深/悬浮 | `#B3BAC8` | 深边框、placeholder |
-| 一般 | `#D8DBE2` | 常规分割线、边框 |
-| 描边 | `#E3E6EA` | 轻量描边 |
-| 灰色背景上的 hover | `#EBEDEE` | 灰背景区域的 hover |
-| 背景灰 | `#F5F6F7` | 次级背景、侧边栏底色 |
-
-### 1.3 文字色
-
-| 色值 | 用途 |
-|------|------|
-| `#101828` | 强调文字、正文标题 |
-| `#475467` | 次强调、大段正文内容 |
-| `#667085` | 次要信息 |
-| `#98A2B3` | 置灰/说明文字 |
-| `#FFFFFF` | 纯白文字(深色背景上) |
-
-### 1.4 语义色 — 成功 (Success Green)
-
-| 状态 | 色值 |
-|------|------|
-| 常规 | `#02B589` |
-| 悬浮 | `#11D4A4` |
-| 按下 | `#04A87F` |
-| 禁用、描边 | `#80DAC4` |
-| 背景 | `#CFF8EE` |
-| 浅色/白色悬浮 | `#E6FFF9` |
-
-### 1.5 语义色 — 警告 (Warning Orange)
-
-| 状态 | 色值 |
-|------|------|
-| 常规 | `#F8AB42` |
-| 悬浮 | `#FFC16C` |
-| 按下 | `#F69D24` |
-| 禁用、描边 | `#FBD5A0` |
-| 背景 | `#FFECD1` |
-| 浅色/白色悬浮 | `#FFF5E8` |
-
-### 1.6 语义色 — 失败/危险 (Error Red)
-
-| 状态 | 色值 |
-|------|------|
-| 常规 | `#FC5D5D` |
-| 悬浮 | `#FF7676` |
-| 按下 | `#F95555` |
-| 禁用、描边 | `#FCAAAA` |
-| 背景 | `#FFDFDF` |
-| 浅色/白色悬浮 | `#FFF0F0` |
-
-### 1.7 Icon 专用色
-
-按需取色,保证页面**蓝色系占比最多**。
-
-| 颜色 | 色值 |
-|------|------|
-| 蓝色 | `#126DFF` |
-| 红色 | `#F25149` |
-| 紫色 | `#7655FA` |
-| 绿色 | `#02B589` |
-| 橙色 | `#F8AB42` |
+## 0. 设计原则(冲突时按顺序取舍)
+
+1. **内容为先**:界面是容器,报告、表格、答案才是主角。界面元素用中性色,颜色只表达含义和**唯一**的主要操作。能用留白分组就不加边框和底色。
+2. **清晰的层级**:每个视图只有一个主要操作。层级靠字号、字重、间距建立,不靠盒子套盒子。
+3. **一致而自然**:同样的东西永远长一样、动一样。动效有来处、有去处、可打断。照顾深浅外观、字号放大、减弱动效。
+4. **过程安静可见**:智能体在思考/检索/调用工具时,用**一行安静的文字**说清在做什么、用了多久;可展开看细节,默认不打扰。不要多处同时闪动的指示器。
---
-## 2. 文字排版
+## 1. 色彩
+
+### 1.1 品牌色
+
+| 名称 | 色值 | 用途 |
+|---|---|---|
+| 环抱蓝 Hug Blue | `#126DFF`(白字 4.51:1) | **唯一强调色**:主要操作、链接、选中、焦点 |
+| 火花绿 Spark | `#19D3A7` | **只属于标志和品牌动画,界面中禁用**(与成功色亮度对比仅 1.37:1,分不清) |
+| 墨 | `#0B0D12` | 文字、深色底(宣传) |
+| 纸 | `#FFFFFF` | 浅色底 |
+
+- 深色模式主色**填充仍用 `#126DFF`**(v1 的 `#3E8BFF` 白字只有 3.31:1,P0);深色下的链接/强调文字改用 `--color-primary-text`(`#5C9BFF`)。
+
+### 1.2 语义色:每种只有三个令牌
+
+| 语义 | 填充(状态点、实心按钮) | 前景(文字/图标,≥4.5:1) | 浅底(提示条、标签) |
+|---|---|---|---|
+| 强调 | `--color-primary` #126DFF | `--color-primary-text` #0F62E6 | `--color-primary-light` #EBF2FF |
+| 成功 | `--color-success` #02B589 | `--color-success-text` #067A5E | `--color-success-bg` #E6FFF9 |
+| 警告 | `--color-warning` #F8AB42 | `--color-warning-text` #B45309 | `--color-warning-bg` #FFF5E8 |
+| 错误 | `--color-error` #FC5D5D | `--color-error-text` #D92D20 | `--color-error-bg` #FFF0F0 |
+| 破坏性实心按钮 | `--color-danger-fill` #D92D20(白字 4.83:1) | — | — |
+
+规则:
+- **语义色不能直接当文字色用**(v1 的 success/warning/error 当文字只有 2.63/1.93/3.06:1,P0)。文字、图标一律用 `*-text`。
+- **取消 hover/active/disabled 色阶**(v1 的 `--color-*-hover/-active/-disabled` 不再用于新代码)。状态改为统一叠加层,见 1.3。
+- 浅底上的文字用对应的 `*-text`,不要用填充色压浅底(v1 危险按钮红字压浅红底 2.76:1,P0)。
-### 2.1 字体栈
+### 1.3 状态叠加层(hover / press / disabled)
-| 平台 | 字体 | 字重 |
-|------|------|------|
-| macOS | PingFang SC | 正常 (400) / 加粗 (600) |
-| Windows | 微软雅黑 | 正常 (400) / 加粗 (700) |
-| 特殊数字/汉字 | 钉钉进步体 / DingTalk Sans | 正常 (400) |
+颜色只会**变深**,白字对比度不会下降:
+
+| 状态 | 中性控件(透明/浅底) | 彩色填充(主按钮等) |
+|---|---|---|
+| 悬停 | 叠 `--state-hover`(浅 `rgba(16,24,40,.04)` / 深 `rgba(255,255,255,.06)`) | 叠 `--state-on-fill-hover`(黑 8% / 深色黑 12%) |
+| 按下 | 叠 `--state-press`(.08 / .10),并 `scale(.97)` | 叠 `--state-on-fill-press`(黑 16% / 深色黑 22%),并 `scale(.97)` |
+| 禁用 | 整体 `opacity: .4` | 整体 `opacity: .4` |
+
+写法(叠加而不是换色):
-**CSS font-family 推荐写法(与 variables.css 实现一致,系统栈优先):**
```css
-font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
- "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
+.jx-xxx-btn { background: var(--color-primary); color: var(--color-on-primary); }
+.jx-xxx-btn:hover { background-image: linear-gradient(var(--state-on-fill-hover), var(--state-on-fill-hover)); }
+.jx-xxx-btn:active { background-image: linear-gradient(var(--state-on-fill-press), var(--state-on-fill-press)); transform: scale(.97); }
+.jx-xxx-btn:disabled { opacity: .4; }
```
-### 2.2 字号层级
+antd 侧:`colorPrimaryHover` / `colorPrimaryActive` 设为**更深**的 `#0F62E6` / `#0A56CC`(见 `assets/design-v2/tokens/appTheme.v2.ts`)。
-| 字号 | 字重 | 用途 |
-|------|------|------|
-| 12px | 常规 | 辅助文案、标签 |
-| 14px | 常规 | 正文 - 常规(默认正文) |
-| 14px | 加粗 | 小标题 |
-| 16px | 常规 | 正文 - 大字号 |
-| 16px | 加粗 | 常规标题 |
-| 18px | 加粗 | 特殊标题和强调 |
-| 22px | 加粗 | 页面一级标题 |
-| 44px | 加粗 | 页面 banner、运营等标题 |
+### 1.4 文字与背景
-### 2.3 日期格式
+文字三级(**占位符 = 三级文字色**,不再用 2.58:1 的 `#98A2B3`):
-- 用 `/` 分隔:`2025/08/18 14:08`
-- 到最末右对齐
+| 令牌 | 浅色 | 深色 | 用途 |
+|---|---|---|---|
+| `--color-text` | #101828 | #E8ECF4 | 正文、标题 |
+| `--color-text-secondary` | #475467 | #B3BDCD | 说明 |
+| `--color-text-tertiary` | #667085 | #8792A4 | 辅助、时间戳、**占位符** |
----
+背景与线(深色模式层级越高表面越亮,不靠加重投影):
+
+| 令牌 | 浅色 | 深色 | 用途 |
+|---|---|---|---|
+| `--color-bg-base` | #FFFFFF | #0F141B | 页面底色 |
+| `--color-bg-layout` | #F5F7FB | #12171F | 侧栏、分区底 |
+| `--color-bg-container` | #FFFFFF | #161C25 | 卡片、面板 |
+| `--color-bg-elevated` | #FFFFFF | #1C2330 | 浮层、弹窗 |
+| `--color-bg-chat` | #FDFDFC | #111720 | 对话区底 |
+| `--color-separator` | #E3E8EF | #2B3442 | 分隔线(装饰性;即 v1 的 `--color-border`) |
+| `--color-control-border` | #C5CCD8 | #3A4556 | 输入框边框(配合可见标签) |
+| `--color-fill-quiet` | #F2F4F7 | #1F2631 | 次要按钮、胶囊、用户气泡 |
+| `--material` | rgba(255,255,255,.82) | rgba(28,35,48,.80) | 浮层材质,配合 `backdrop-filter: blur(20px)` |
+| `--scrim` | rgba(16,24,40,.32) | rgba(0,0,0,.56) | 模态遮罩 |
-## 3. 图标
+### 1.5 分类色:从 7 组收敛到 4 组
-- **推荐图标库**: IconPark(字节跳动)
-- **最小切图尺寸**: 16px,其他尺寸为 4 的倍数
+只用于区分类别,**类别优先用图标和文字区分,颜色只做辅助**:
-### 3.1 线性图标
+| 组 | 示例 | 令牌(浅底 + 前景成对使用) |
+|---|---|---|
+| 蓝 | MCP / 连接器 | `--color-tint-blue-bg` + `--color-tint-blue` |
+| 紫 | 技能 | `--color-tint-purple-bg` + `--color-tint-purple` |
+| 青 | 自动化 | `--color-tint-teal-bg` + `--color-tint-teal` |
+| 琥珀 | 计划模式 | `--color-tint-amber-bg` + `--color-tint-amber` |
+
+新代码不要再用 `cyan / green / red` 三组 tint(存量保留,不新增引用)。
+
+---
-| 尺寸 | 描边宽度 | 圆角 | 端点 |
-|------|---------|------|------|
-| 32px | 2.4px 居中 | 2px | 圆形 |
-| 16px | 1.2px 居中 | 1px | 圆形 |
+## 2. 文字层级
-> 使用时只根据 32px / 16px 两种基准尺寸,通过缩放功能来变化大小。
+按**角色**命名(参考 Apple 文字样式),九级覆盖所有场景。**字重只用 400 / 500 / 600**(不再出现 700/900)。中文不加负字距;西文 28px 以上收紧。**字号一律用 rem**,跟随浏览器缩放。
-### 3.2 面性图标 — 单色
+| 角色 | 令牌前缀 | 字号 / 行高 / 字重 / 字距 | 用途 | 与 v1 |
+|---|---|---|---|---|
+| 展示 display | `--font-size-display` | 2.5rem(40) / 48 / 600 / -0.02em | 欢迎语、宣传 | 调整(原 xxl 44) |
+| 标题一 title-1 | `--font-size-title-1` | 1.75rem(28) / 36 / 600 / -0.01em | 页面标题 | 新增 |
+| 标题二 title-2 | `--font-size-title-2` | 1.375rem(22) / 30 / 600 | 分区标题 | 沿用 xl |
+| 标题三 title-3 | `--font-size-title-3` | 1.125rem(18) / 26 / 600 | 弹窗、卡片标题 | 沿用 lg |
+| 重点 headline | `--font-size-headline` | 0.9375rem(15) / 22 / 600 | 列表项标题 | 新增 |
+| 阅读 body-lg | `--font-size-body-lg` | 1rem(16) / 28 / 400 | 对话、文档正文 | 沿用 md |
+| 正文 body | `--font-size-body` | 0.875rem(14) / 22 / 400 | **界面默认**:按钮、输入、列表、表格 | 沿用 sm |
+| 注释 footnote | `--font-size-footnote` | 0.8125rem(13) / 20 / 400 | 过程行、说明 | 新增 |
+| 说明 caption | `--font-size-caption` | 0.75rem(12) / 18 / 400 | 时间戳、标签 | 沿用 xs |
-- 尺寸 32px,背景圆角 8px
+每级同时有 `--line-height-*` / `--font-weight-*` / `--letter-spacing-*` 四件套(见 v2 token CSS)。
-### 3.3 面性图标 — 双色
+字体(已在代码中落地,`platformTypography.ts` 在挂载前给 `` 打 `data-font-platform`):
-- 同色系,主颜色 100%,浅色 50%
+| 场景 | 字体 |
+|---|---|
+| 界面中文 | Apple: PingFang SC · Windows: Microsoft YaHei UI · 其他: Noto Sans CJK SC(`--font-family`) |
+| 数字 | DingTalk Sans → 系统字体(`--font-family-number`);**表格、计时、金额一律 `font-variant-numeric: tabular-nums`** |
+| 代码 | ui-monospace, SF Mono, Menlo, Consolas(`--font-family-mono`) |
+| 宣传 | 思源黑体(不进产品界面) |
---
-## 4. 间距
+## 3. 间距与尺寸
-以 **4px 为基本单位**,使用 4 的倍数作为间距。
+**8 栅格**:`4 / 8 / 12 / 16 / 24 / 32 / 48 / 64`。4 只用于图标和文字之间的半步。v1 的 20 与 40 不在栅格上,**新代码不再使用**。
-| 值 | 常见用途 |
-|-----|---------|
-| 4px | 紧凑间距(标签内边距、小图标间距) |
-| 8px | 元素间常规间距 |
-| 16px | 卡片内间距、段落间距 |
-| 20px | 模块间距、表单行距 |
-| 40px | 大区域分隔 |
+| v2 令牌 | 值 | 对应 v1 |
+|---|---|---|
+| `--space-1` | 4 | `--space-xs` |
+| `--space-2` | 8 | `--space-sm` |
+| `--space-3` | 12 | (v1 缺失) |
+| `--space-4` | 16 | `--space-md` |
+| `--space-6` | 24 | 替代 `--space-lg` 20 |
+| `--space-8` | 32 | (v1 缺失) |
+| `--space-12` | 48 | 替代 `--space-xl` 40 |
+| `--space-16` | 64 | — |
+
+尺寸规则:
+- **点击区**:桌面 ≥ 32×32,触屏 ≥ 44×44(真实尺寸)。紧凑控件最小 28,用伪元素/padding 外扩点击区。
+- **控件高度**:28(紧凑,仅桌面表格和工具栏)· 32(默认)· 40(主要操作、移动端)。不再出现 24px 高的按钮。
+- **阅读宽度**:对话与文档正文最大 **720px**(每行约 40 个汉字)。
+- **页面边距**:桌面 24 / 32,移动端 16 + 安全区(`env(safe-area-inset-*)`)。
---
-## 5. 圆角
+## 4. 圆角
+
+值不变,规则变严:**圆角跟随尺寸,且同心——内层圆角 = 外层圆角 − 内边距**。
-系统使用**大圆角**表达 AI、时尚、亲近的品牌感。
+| 令牌 | 值 | 用于 |
+|---|---|---|
+| `--radius-xs` | 6 | 28px 控件、代码块 |
+| `--radius-sm` | 10 | 32–40px 控件、菜单项 |
+| `--radius-md` | 16 | 卡片、菜单、弹窗 |
+| `--radius-lg` | 24 | 对话输入区、面板 |
+| `--radius-full` | 999px | 标签、头像、开关、独立图标按钮(胶囊) |
-| 圆角值 | 变量 | 用途 | 示例 |
-|--------|------|------|------|
-| 6px | `--radius-xs` | 小面积元素 | 标签、小卡片 |
-| 10px | `--radius-sm` | 常用组件 | 按钮、输入框、下拉框、菜单 |
-| 16px | `--radius-md` | 分割模块 | 卡片视图、背景卡片 |
-| 24px | `--radius-lg` | 大容器 | 弹窗、大输入框 |
+例:菜单外 16、内边距 6 → 选项 10(v1 外 10 / 内边距 6 / 选项 6 不同心,P1)。
---
-## 6. 投影
+## 5. 层级与材质
-为整体性统一限制投影层级,分为两种状态:
+一共四层。**投影只给浮在内容之上的东西。**
-- **常态**: 轻微投影(几乎无感知)
-- **悬停/hover**: 加深投影(凸显层级)
+| 层 | 背景 | 内容 | 投影 |
+|---|---|---|---|
+| L0 底面 | `--color-bg-layout` / `--color-bg-base` | 页面、侧栏 | 无 |
+| L1 内容 | `--color-bg-container` | 卡片、面板 | **静止无投影**,悬停出现 `--shadow-raised` 并上浮 1px |
+| L2 浮层 | `--material` + `backdrop-filter: blur(20px)` | 菜单、弹出框、提示 | `--shadow-floating` |
+| L3 模态 | `--color-bg-elevated` + `--scrim` 遮罩 | 对话框、表单抽屉 | `--shadow-floating` |
-可根据具体场景微调投影颜色。
+- 静止卡片不加投影,用底色和留白分组(替代 v1 `--shadow-card`)。
+- **对话输入区只保留投影(`--shadow-raised`),不再同时加边框**。
+- 投影令牌:`--shadow-raised`(原 card)、`--shadow-floating`(原 dropdown),深浅两档已定义。
---
-## 7. 按钮
+## 6. 图标
-最小切图尺寸 **36px**,其他为 4 的倍数。
+参考 SF Symbols:**图标是文字的一部分**。
-### 7.1 按钮类型
+- 24px 画板,四周留 2px,**线宽 1.5px**,圆头圆角(`stroke-linecap/linejoin: round`),`stroke="currentColor"`。
+- 尺寸跟随字号、线宽跟随字重、颜色继承文字色:13px 常规 → 线宽 1.5;15px 中粗 → 2;20px 标题 → 2。
+- 默认线性,**选中态换面性**。
+- 风格样例:`assets/design-v2/icons-ui/`(12 个:new-chat / search / knowledge / skill / automation / my-space / attachment / settings / send / chevron-right / check / close)。
+- 既有 IconPark / `@ant-design/icons` 可继续用,但要满足上面的线宽与 currentColor 规则;不要给图标单独上品牌色。
-| 类型 | 说明 |
-|------|------|
-| 主要按钮 | 蓝色填充(`#126DFF`),白色文字 |
-| 次要按钮 | 白色填充、蓝色描边 |
-| 文字按钮 | 无边框、无背景,文字色即操作色 |
-| 图标按钮 | 仅图标,无文字 |
-| 成功按钮 | 绿色填充(`#02B589`) |
-| 危险按钮 | 红色填充(`#FC5D5D`) |
+---
-### 7.2 按钮尺寸
+## 7. 组件
-| 尺寸 | 高度(参考) |
-|------|------------|
-| 大 | 40px |
-| 中(默认) | 36px |
-| 小 | 28px |
+以 Ant Design 为底,用 v2 令牌覆盖(ConfigProvider token,不覆写组件内部 DOM 样式)。
-### 7.3 按钮状态
+### 7.1 按钮
-每种按钮均有 4 种状态:常规 → 悬浮(hover) → 按下(active) → 禁用(disabled)。
+- **一个视图只有一个主要按钮**(`type="primary"`)。
+- 次要操作用**浅灰填充**(`--color-fill-quiet`),**不再用描边按钮**。
+- 低频操作用文字按钮(`type="text"` / `type="link"`)。
+- 破坏性操作:**确认框里用实心红**(`--color-danger-fill` + 白字),**列表里用红色文字按钮**(`--color-error-text`)。
+- 尺寸 28 / 32 / 40;加载中按钮文案写进行时("生成中")。
+- 文案写动作:"生成报告",不写"立即开启智能分析之旅"。
----
+### 7.2 输入框与对话输入区
-## 8. 导航
+- 输入框**必须有可见标签**,不能只靠占位符说明用途。占位符写示例("例如:Q3 华东区销售分析")。
+- 出错时**写清原因和改法**:"请输入 00:00 到 23:59 之间的时间"。
+- 边框用 `--color-control-border`。
+- **对话输入区**是最重要的控件:`--radius-lg` 大圆角,**只有投影没有边框**;内容为空时发送按钮是中性色,**有内容后才变主色**(主色只在能操作时出现)。
-### 8.1 导航菜单
+### 7.3 对话与过程
-- 垂直列表,图标 + 文字
-- 选中项高亮
+- 用户消息:浅灰气泡(`--color-fill-quiet`)。助手回复**不加气泡**。
+- 思考、工具调用:**默认一行安静的文字**——做了什么、结果多少、用了多久,点击展开细节。
+ 例:"已思考 12 秒"、"已检索知识库「销售数据 2026」14 条 · 0.8s"、"正在生成 Excel 报表"。
+- **只有用户会打开的产物**(文件、表格、网页)才做成卡片。v1 把每次工具调用做成卡片(过程比答案显眼,P1),新代码不要这样。
+- 正文宽度 ≤ 720px,阅读字号 body-lg(16/28)。
-### 8.2 Tabs / 标签页
+### 7.4 状态
-- **一级**: 页面或模块的一级导航,有选中样式、默认样式、hover 样式
-- **二级**: 页面或模块的二级导航
-- **三级**: 紧凑标签切换
+状态**同时用颜色和形状**表达。**一屏只有一个持续动画。**
-### 8.3 面包屑 / 返回
+| 状态 | 表现 |
+|---|---|
+| 运行中(当前那一项) | 文字微光(shimmer),一屏一个 |
+| 运行中(其他项) | 静止蓝点 |
+| 已完成 | 成功色勾,`statusIn` 落定 |
+| 等你确认 | 警告色点 + **扩散涟漪**(唯一允许涟漪的状态,因为它在叫人) |
+| 失败 | 错误色 |
+| 排队中 / 已暂停 | 中性色静止 |
+| 计数徽标 | 超过 99 显示 `99+` |
-- **面包屑**: 显示页面在系统层级结构中的位置,并提供上返回,本系统中较少使用
-- **返回**: 用于二级页面,能向上返回
+### 7.5 卡片与浮层
+
+- 卡片静止无投影,悬停出现 `--shadow-raised` 并 `translateY(-1px)`。
+- 菜单用材质背景,**从触发点展开**(`transform-origin` 指向触发点),`--radius-md` + 选项 `--radius-sm`。
+- 提示条(toast):**反色胶囊**,出现在**底部中间**,3 秒后自动消失,新提示替换旧提示。
+- 快捷键提示(⌘N / ⌘K)用 caption 字号、三级文字色。
---
-## 9. 数据录入
+## 8. 动效
-### 9.1 输入框
+### 8.1 原则
-- 标签对齐方式在页面内统一(左对齐 / 右对齐 / 顶对齐)
-- 默认高度 36px
-- 前后间距 20px(页面范围统一)
-- 前后间距需要在整个页面范围统一
-- 可附带前缀 icon
+1. **有来处**:菜单从按钮展开,面板从边缘滑入,消息从输入框升起;返回沿原路离开。
+2. **可打断**:手势、拖拽、展开收起、布局变化都用弹簧;中途打断从当前位置和速度继续。动画期间界面始终可操作。
+3. **克制**:产品界面弹簧不回弹或只轻微回弹;一次性过渡 ≤ 320ms;除折叠展开外**只动 `opacity` 和 `transform`**,不动 filter(含 blur)和阴影。
+4. **进行中常在**:表示"正在运行"的动画在减弱动效时也继续;装饰性位移在减弱动效时换成淡入淡出。
-### 9.2 下拉选择
+### 8.2 弹簧(Motion 库,用时长 + 回弹描述)
-- 包含下拉选择 + 搜索
-- 基础样式有正文类型、hover 状态
-- 有单选和多选模式
-- 支持复选框列表
+| 预设 | 参数 | 用于 |
+|---|---|---|
+| `SPRING.smooth` | `visualDuration: .35, bounce: 0` | 面板、抽屉、折叠展开、布局变化(替代 v1 `panel`) |
+| `SPRING.snappy` | `visualDuration: .25, bounce: .15` | 标签页指示条、开关、徽标确认(替代 v1 `pop` / `ink`) |
-### 9.3 日期选择器
+v1 的 `pop` / `panel` 回弹 33%(面板会晃,P1),`ink` 一并标记弃用;**新代码只用 smooth / snappy**,`bouncy` 不进产品界面。
-- 标准日历面板
-- 支持日期范围选择
-- 支持快捷选项(本周/上周/本月等)
+### 8.3 时长与曲线(不适合弹簧的一次性过渡)
-### 9.4 多选 / 单选
+| 令牌 | 时长 | 用于 |
+|---|---|---|
+| `--motion-duration-instant` | 100ms | 悬停着色、按下反馈 |
+| `--motion-duration-fast` | 160ms | 状态切换、菜单弹出、图标落定 |
+| `--motion-duration-normal` | 240ms | 入场、出场、消息发送、列表编排 |
+| `--motion-duration-slow` | 320ms | 面板转场、折叠展开(能用弹簧优先弹簧) |
-- 单选: 圆形 radio,选中为蓝色填充
-- 多选: 方形 checkbox,选中为蓝色勾选
-- 有横排和竖排两种布局
+曲线三条:`--motion-ease-brand-out`(入场/展开/升起)、`--motion-ease-standard`(双向状态切换)、`--motion-ease-exit`(退场)。
+**退场时长 = 入场 × 0.7**(如菜单 160 → 112ms,提示条 240 → 168ms)。
+**禁止**在代码里写 `ease` / `ease-out` / 新的 cubic-bezier 值,TS 侧用 `utils/motionTokens.ts` 的 `DUR` / `EASE`。
-### 9.5 开关
+### 8.4 编排
-- 默认灰色背景
-- 开启状态为主色蓝(`#126DFF`)
-- 带有开/关文字标识
+- 静态列表入场逐项错开 60ms(`--motion-stagger-step` / `STAGGER_STEP`),**第 8 项起不再增加延迟**(`STAGGER_CAP`)。
+- 超过 20 项的动态列表不做布局动画(`LAYOUT_ANIM_MAX_ITEMS`)。
+- 翻看历史记录不播任何入场动画,只对 **2 秒内新产生**的内容播放(`FRESH_ENTER_WINDOW_MS`)。
----
+### 8.5 原语库(新增动画前先找同语义的原语)
-## 10. 数据展示
+命名:关键帧 `jx-kf-名称`,工具类 `jx-anim-名称`(定义在 `styles/motion.css`)。**入场类一律 `animation-fill-mode: backwards`。**
-### 10.1 标签 (Tag)
+| 原语 | 工具类 | 参数 | 用于 | 减弱动效时 |
+|---|---|---|---|---|
+| fadeInUp | `.jx-anim-fadeInUp` | 240ms · brand-out · 上移 8px | 卡片、确认条入场 | 淡入 |
+| fadeIn | `.jx-anim-fadeIn` | 240ms · standard | 大面积区域 | 淡入 |
+| scaleIn | `.jx-anim-scaleIn` | 160ms · brand-out · .97 → 1 | 菜单、弹出框(原点指向触发点) | 淡入 |
+| statusIn | `.jx-anim-statusIn` | 160ms · brand-out · .7 → 1 | 状态图标落定 | 淡入 |
+| flash | `.jx-anim-flash` | 1.2s · standard | 跳转定位后的一次性高亮 | 保持 |
+| shake | `.jx-anim-shake`(`jx-kf-shakeX`) | 300ms · ±3px | 校验失败 | 停止 |
+| shimmer 文字 | 关键帧 `jx-kf-shimmer` | 2s linear 循环 | 运行中的那一行(一屏一个) | 保持(`motion-keep`) |
+| ripple | `.jx-anim-ripple` | 1.6s 循环 | **只用于"等你确认"** | 停止 |
+| stagger | `.jx-anim-stagger` + `staggerStyle(i)` | 60ms 步进,封顶第 8 项 | 静态列表入场 | 淡入 |
-- 慎重使用标签,保证页面干净有序
-- 同一信息前台展现阶段对比最多使用 2 种颜色样式
-- 类型:基础标签、可关闭标签、表态标签
+> 代码里另有 `.jx-anim-pulse`(`motion-keep` 的通用"进行中"脉冲)与 `.jx-anim-flash-row`。v2 要求一屏只有一个持续动画:
+> 运行中的当前项优先用文字微光,其他运行项保持静止蓝点,不要为每一项都挂 pulse。
-### 10.2 卡片 (Card)
+### 8.6 智能体动效(一次任务的完整过程)
-- padding: 16–20px(可根据内容调整,特殊情况为 4 的倍数)
-- 类型一:图标 + 标题 + 描述 + 标签
-- 类型二:大面积内容卡片(标题 + 正文段落)
-- 圆角 12px
+同一时刻只有一个微光在动;文字按词块淡入,不做模糊,不逐字排队。
-### 10.3 气泡提示卡片
+| 环节 | v2 | v1(不要再这样写) |
+|---|---|---|
+| 发送 | 用户气泡 `translateY(8px) + scale(.98) → 1`,240ms brand-out,原点右下 | scale .92 · 300ms ease-out |
+| 思考 | 一行"正在思考 · 8 秒",文字微光 2s linear,计时每秒更新;骨架屏只在加载历史时出现,不与微光同屏 | 跳点 + 呼吸 + 骨架三处同时动 |
+| 调用工具 | 进行中:文字微光;完成:图标 `statusIn` 160ms,只留下一行可展开的记录 | 卡片 + 无限涟漪 |
+| 流式输出 | **按词块淡入**,opacity 180ms standard,落后模型 ≤ 300ms | 逐字 350ms + 65ms 错开 + blur 3px(每秒约 15 字,越积越多) |
-- 点击/鼠标移入元素时弹出气泡式卡片浮层
+### 8.7 交互控件
-### 10.4 Tips 提示
+| 控件 | 动效 |
+|---|---|
+| 开关 | 滑块 snappy 弹簧;底色 160ms standard |
+| 标签页 | 指示条位置与宽度 snappy 弹簧;文字颜色 160ms standard |
+| 面板 / 抽屉 | smooth 弹簧从边缘滑入;遮罩透明度跟随同一进度 |
+| 菜单 | 打开 160ms brand-out `scale(.97 → 1)`;关闭 112ms exit;原点在按钮处 |
+| 提示条 | 入场 240ms brand-out 上移 16px;退场 168ms exit;3 秒后消失 |
+| 折叠展开 | 高度与透明度 smooth 弹簧;箭头旋转 240ms brand-out |
-- 用于展示不完全的内容,鼠标移入时展示完整内容
-- 有浅色和深色两种样式
+### 8.8 减弱动效(`prefers-reduced-motion: reduce`)
-### 10.5 表格 (Table)
+v2 改为 Apple 做法:**位移和缩放换成淡入淡出,时长不变**;循环装饰停止;运行指示器继续。
-- 标准表格:表头 + 数据行 + 分页
-- 特殊表格:带颜色标记行
+| 类型 | 减弱动效时 | 写法 |
+|---|---|---|
+| 入场位移 / 缩放 | 淡入,时长不变 | 同文件 `@media (prefers-reduced-motion: reduce)` 内写**同名 `@keyframes` 覆盖版,只保留 opacity**,标注 `motion-fade` |
+| 弹簧(Motion 库) | 换成透明度过渡 | `useReducedMotion()` 为真时只动 opacity,时长取 `DUR.fast` |
+| 循环装饰(涟漪、抖动) | 停止 | 覆盖版定格,标注 `motion-stop` |
+| 工作指示器(微光、转圈、骨架) | 继续 | 使用它的规则上标注 `motion-keep: 理由` |
+| CSS transition | 归零 | 保持现状(改成正数会让 antd 浮层定位也产生过渡) |
----
+> 构建门禁 `scripts/check-loading-motion.mjs` 保留。**当前门禁仍按 v1 规则检查**:装饰性关键帧必须有同名
+> 覆盖版(v1 为"定格终态")或在使用处标注 `motion-keep:`。按 v2 写"仅 opacity"的同名覆盖版同样满足门禁;
+> `motion-fade` / `motion-stop` 标注随迁移清单第 3 项一起被门禁识别。
-## 11. 数据提示
+---
-### 11.1 Alert 警告提示
+## 9. 文案语气
-4 种语义,每种有可关闭和不可关闭两种形态:
+像一位靠谱的同事说话:直接、具体、不卖弄。
-| 类型 | 颜色 |
-|------|------|
-| 成功 | 绿色系 (`#02B589`) |
-| 注意 | 橙色系 (`#F8AB42`) |
-| 失败 | 红色系 (`#FC5D5D`) |
-| 提示信息 | 蓝色系 (`#126DFF`) |
+| 场景 | ✅ | ❌ |
+|---|---|---|
+| 按钮 | 写动作:生成报告 | 立即开启智能分析之旅 |
+| 进行中 | 写正在做什么:正在检索 3 个知识库 | AI 正在努力思考中… |
+| 出错 | 写原因和办法:文件超过 50MB,请压缩后重新上传 | 哎呀,出错了! |
+| 完成 | 写结果在哪:报告已保存到我的空间 | 操作成功 |
-### 11.2 全局提示 (Message)
+所有用户可见文案仍须包 `t()`(见 SKILL.md i18n 节)。
-- 顶部居中弹出,自动消失
-- 类型:成功 / 注意 / 失败
+---
-### 11.3 弹窗 (Modal)
+## 10. 品牌标志(界面内用到时)
-- 标题 + 内容区 + 底部操作栏(取消 + 确认/保存)
-- 确认按钮为主色蓝,危险操作为红色
-- 圆角 20px
+- **主标志是标准字**(A 里已含环线和火花点)。**禁止方形标与标准字并排**。产品顶栏这类横向空间用标准字。
+- 方形标只用于方形容器(应用图标、头像、网页图标)。**24px 及以下用小尺寸版** `hugagentos-mark-small.svg` / `favicon.svg`。
+- 彩色稿里火花点永远是火花绿;只有单色稿可以让火花点跟随主体变色。
+- 禁止:拉伸压扁、换品牌色、旋转倾斜、投影/描边/渐变、火花点改色或移位。
+- 对话中的助手头像:品牌蓝底 + 白色环线 + 火花绿点,环线占圆形头像宽度 60%。
+- 标志动画 ≤ 1 秒(描边 0–600ms standard → 填实 500–700ms → 火花点 650ms 起 snappy 弹出);启动页只播一次;减弱动效时整体 240ms 淡入。
+- 素材:`assets/design-v2/logo/`(mark / mark-small / reverse / mono / glyph / favicon)。
---
-## 12. CSS 变量映射(推荐)
+## 11. Ant Design 主题映射(v2 目标值)
-以下为设计规范到 CSS 变量的推荐映射,应用在 `variables.css` 中:
+真源 `src/frontend/src/appTheme.ts`,参考实现 `assets/design-v2/tokens/appTheme.v2.ts`:
-```css
-:root {
- /* 主色 */
- --color-primary: #126DFF;
- --color-primary-hover: #3C87FF;
- --color-primary-active: #0862F3;
- --color-primary-disabled: #A0C5FF;
- --color-primary-bg: #DBE9FF;
- --color-primary-light: #EBF2FF;
-
- /* 边框与填充 */
- --color-fill-heavy: #8C94A2;
- --color-fill-deep: #B3BAC8;
- --color-fill: #D8DBE2;
- --color-border: #E3E6EA;
- --color-fill-hover: #EBEDEE;
- --color-bg-gray: #F5F6F7;
-
- /* 文字 */
- --color-text: #101828;
- --color-text-secondary: #475467;
- --color-text-tertiary: #667085;
- --color-text-placeholder: #98A2B3;
- --color-text-white: #FFFFFF;
-
- /* 成功 */
- --color-success: #02B589;
- --color-success-hover: #11D4A4;
- --color-success-active: #04A87F;
- --color-success-disabled: #80DAC4;
- --color-success-bg: #CFF8EE;
- --color-success-light: #E6FFF9;
-
- /* 警告 */
- --color-warning: #F8AB42;
- --color-warning-hover: #FFC16C;
- --color-warning-active: #F69D24;
- --color-warning-disabled: #FBD5A0;
- --color-warning-bg: #FFECD1;
- --color-warning-light: #FFF5E8;
-
- /* 错误/危险 */
- --color-error: #FC5D5D;
- --color-error-hover: #FF7676;
- --color-error-active: #F95555;
- --color-error-disabled: #FCAAAA;
- --color-error-bg: #FFDFDF;
- --color-error-light: #FFF0F0;
-
- /* Icon 色 */
- --color-icon-blue: #126DFF;
- --color-icon-red: #F25149;
- --color-icon-purple: #7655FA;
- --color-icon-green: #02B589;
- --color-icon-orange: #F8AB42;
-
- /* 圆角 */
- --radius-xs: 6px;
- --radius-sm: 10px;
- --radius-md: 16px;
- --radius-lg: 24px;
-
- /* 间距 */
- --space-xs: 4px;
- --space-sm: 8px;
- --space-md: 16px;
- --space-lg: 20px;
- --space-xl: 40px;
-
- /* 字体(系统栈优先) */
- --font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
- "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
- --font-family-number: "DingTalk Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
-
- /* 字号 */
- --font-size-xs: 12px;
- --font-size-sm: 14px;
- --font-size-md: 16px;
- --font-size-lg: 18px;
- --font-size-xl: 22px;
- --font-size-xxl: 44px;
-}
-```
+| Token | 浅色 | 深色 |
+|---|---|---|
+| colorPrimary | #126DFF | **#126DFF**(v1 为 #3E8BFF) |
+| colorPrimaryHover / Active | #0F62E6 / #0A56CC(变深) | 同左 |
+| colorLink | #0F62E6 | #5C9BFF |
+| colorSuccess / colorError | #02B589 / #D92D20 | #22C79D / #FF6B6B |
+| colorSuccessText / WarningText / ErrorText | #067A5E / #B45309 / #D92D20 | #3DDBB0 / #FFC26B / #FF8A80 |
+| colorText / Secondary / Tertiary | #101828 / #475467 / #667085 | #E8ECF4 / #B3BDCD / #8792A4 |
+| colorTextPlaceholder | #667085 | #8792A4 |
+| colorBorder / BorderSecondary | #C5CCD8 / #E3E8EF | #3A4556 / #2B3442 |
+| colorBgContainer / Layout / Elevated | #FFFFFF / #F5F7FB / #FFFFFF | #161C25 / #12171F / #1C2330 |
+| borderRadius / LG / SM | 10 / 16 / 6 | 同左 |
+| fontSize / SM / LG | 14 / 12 / 16 | 同左 |
+
+`AppThemeProvider.tsx` 在系统开启减弱动效时额外给 antd 传 `motion: false`(已实现,保持)。
---
-## 13. Ant Design 主题映射
-
-使用 Ant Design ConfigProvider 时,按以下规范配置 token:
-
-```typescript
-const theme = {
- token: {
- colorPrimary: '#126DFF',
- colorSuccess: '#02B589',
- colorWarning: '#F8AB42',
- colorError: '#FC5D5D',
- colorInfo: '#126DFF',
- colorText: '#101828',
- colorTextSecondary: '#475467',
- colorTextTertiary: '#667085',
- colorTextQuaternary: '#98A2B3',
- colorBorder: '#E3E6EA',
- colorBorderSecondary: '#D8DBE2',
- colorBgContainer: '#FFFFFF',
- colorBgLayout: '#F5F6F7',
- borderRadius: 10,
- borderRadiusLG: 16,
- borderRadiusSM: 6,
- fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif',
- fontSize: 14,
- fontSizeSM: 12,
- fontSizeLG: 16,
- },
-};
-```
+## 12. 迁移清单(按风险从低到高)
+
+前四项**只加不删、无视觉变化**,可以先合入;后面几项会改变现有界面,需要逐页走查 / 产品评审。
+
+| # | 改动 | 文件 | 影响 |
+|---|---|---|---|
+| 1 | 新增 `*-text`、`danger-fill`、`state-*`、`fill-quiet`、`brand-spark`、`material`、`scrim`、`radius-full`、`space-1…16`、文字层级、`shadow-raised/floating` 令牌 | variables.css · appTheme.ts | 只加不删 |
+| 2 | SPRING 增加 smooth / snappy,旧 pop / ink / panel 标记 `@deprecated` | utils/motionTokens.ts | 新代码用新预设 |
+| 3 | 减弱动效:位移类覆盖版从"定格"改为"仅 opacity",门禁识别 `motion-fade` / `motion-stop` | styles/motion.css · scripts/check-loading-motion.mjs | 只影响开启减弱动效的用户 |
+| 4 | 新增小尺寸方形标,网页图标 16 / 32 换用 | public/ · index.html | 网页图标更清晰 |
+| 5 | 语义色当文字处换成 `*-text` | 全局搜 `color: var(--color-(success|warning|error))` | 提示文字变深 |
+| 6 | 主按钮悬停 / 按下改为叠加层;深色主色改回 #126DFF | appTheme.ts · variables.css | 悬停由变浅改为变深 |
+| 7 | 占位符改为三级文字色;输入框补齐可见标签 | variables.css · 各表单 | 占位符变深 |
+| 8 | `--space-lg` 20 → 24、`--space-xl` 40 → 48 | variables.css · 引用处 | 需逐页走查 |
+| 9 | 静止卡片去掉 `shadow-card`;对话输入区去掉边框 | 各卡片样式 · composer | 需走查 |
+| 10 | 用户气泡入场 .98 + 8px / 240ms;流式输出改为词块淡入,去掉 blur | chat/messages.css · chat/grapheme-reveal.css | 需体验评审 |
+| 11 | 工具调用默认折叠为一行;运行中改为文字微光,涟漪只留给待确认 | tool.css · 工具调用组件 | 需产品评审 |
+| 12 | 菜单圆角 16、选项 10(同心) | antd Dropdown 主题 · 自研菜单 | 视觉微调 |
+
+做第 1、2 项时直接以 `assets/design-v2/tokens/` 下的 CSS / TS 为准,深浅两档一起加;第 5 项起每一项都是**会改变现有界面**的改动,没有用户明确要求不要顺手做。
diff --git a/.claude/skills/hugagent-frontend-dev/scripts/scaffold_component.sh b/.claude/skills/hugagent-frontend-dev/scripts/scaffold_component.sh
index dfe21e1c..b357a282 100755
--- a/.claude/skills/hugagent-frontend-dev/scripts/scaffold_component.sh
+++ b/.claude/skills/hugagent-frontend-dev/scripts/scaffold_component.sh
@@ -75,7 +75,10 @@ fi
echo ""
echo "=== 手动步骤 ==="
-echo "1. 在 styles/index.ts 中 import './${COMPONENT_CAMEL}.css'"
+echo "1. 在 styles/index.ts 中 import './${COMPONENT_CAMEL}.css'(或在组件内直接 import)"
echo "2. 按需在父组件中引入 <$COMPONENT />"
+echo "3. 新文案的英文翻译加进 src/i18n/en/ 对应分片(EE 专属进 edition-ee/i18n/)"
+echo "4. 模板里标 (v2) 的令牌先 grep variables.css;缺失则按 references/css-variables.md 补齐深浅两档"
+echo "5. 运行门禁: cd src/frontend && npm run check:i18n && npm run check:dark && npm run check:motion"
echo ""
echo "Done!"
diff --git a/.claude/skills/hugagent-frontend-dev/templates/component.tsx b/.claude/skills/hugagent-frontend-dev/templates/component.tsx
index 153cc176..5c12f6cb 100644
--- a/.claude/skills/hugagent-frontend-dev/templates/component.tsx
+++ b/.claude/skills/hugagent-frontend-dev/templates/component.tsx
@@ -2,92 +2,73 @@
* Component template.
*
* Replace ${ComponentName}, ${componentName}, ${group} with actual names.
- * Create as components/${group}/${ComponentName}.tsx
+ * Create as components/${group}/${ComponentName}.tsx and export it from components/${group}/index.ts.
+ *
+ * Rules (see SKILL.md + references/ui-design-spec.md):
+ * - Every user-visible string goes through t() (npm run build fails otherwise)
+ * - Copy: buttons say the action, errors say cause + fix, success says where the result is
+ * - One primary button per view; secondary actions use the quiet fill, not outlined buttons
+ * - No colors in inline style — put them in the CSS file with tokens
*/
-import React, { useState, useMemo, useCallback, useRef } from 'react';
-import { Button, Space, message } from 'antd';
-// import { useChatStore } from '../../stores';
-// import type { SomeType } from '../../types';
+import { useCallback, useMemo, useState } from 'react';
+import { Button, message } from 'antd';
+import { t } from '../../i18n';
+// import { useSomeStore } from '../../stores';
+// import type { SomeItem } from '../../types';
-// ---------------------------------------------------------------------------
-// Props
-// ---------------------------------------------------------------------------
+interface ${ComponentName}Item {
+ id: string;
+ name: string;
+ active: boolean;
+}
interface ${ComponentName}Props {
- /** 必填属性示例 */
- itemId: string;
- /** 回调示例 */
- onClose: () => void;
- /** 可选属性示例 */
- items?: any[];
+ /** 所属对象 id */
+ ownerId: string;
+ items?: ${ComponentName}Item[];
+ onSaved?: () => void;
}
-// ---------------------------------------------------------------------------
-// Component
-// ---------------------------------------------------------------------------
-
-export function ${ComponentName}({ itemId, onClose, items = [] }: ${ComponentName}Props) {
- // --- Zustand stores (global state) ---
- // const { someState, someAction } = useSomeStore();
-
- // --- Local state ---
- const [loading, setLoading] = useState(false);
- const [editMode, setEditMode] = useState(false);
-
- // --- Refs ---
- const inputRef = useRef(null);
+export function ${ComponentName}({ ownerId, items = [], onSaved }: ${ComponentName}Props) {
+ const [saving, setSaving] = useState(false);
- // --- Derived state (useMemo) ---
- const filteredItems = useMemo(() => {
- return items.filter((item) => item.active);
- }, [items]);
+ const activeItems = useMemo(() => items.filter((item) => item.active), [items]);
- // --- Callbacks (useCallback) ---
- const handleSubmit = useCallback(async () => {
- setLoading(true);
+ const handleSave = useCallback(async () => {
+ setSaving(true);
try {
- // await someApiCall();
- message.success('操作成功');
- onClose();
+ // await saveSomething(ownerId);
+ message.success(t('已保存到我的空间'));
+ onSaved?.();
} catch (e) {
- message.error(`操作失败:${(e as Error).message}`);
+ // Error text: cause + what to do. Prefer the backend message when it is user-facing.
+ message.error(t('保存失败:{reason}', { reason: (e as Error).message }));
} finally {
- setLoading(false);
+ setSaving(false);
}
- }, [itemId, onClose]);
+ }, [ownerId, onSaved]);
- // --- Early return for empty state ---
- if (!filteredItems.length) {
- return (
-
- );
+ if (!activeItems.length) {
+ return {t('还没有内容')}
;
}
- // --- Render ---
return (
-
-
-
{filteredItems.length} 项
-
- setEditMode(!editMode)}>
- {editMode ? '取消' : '编辑'}
-
-
- 提交
-
-
-
+
+
+ {t('{n} 项', { n: activeItems.length })}
+
+ {saving ? t('保存中') : t('保存')}
+
+
-
- {filteredItems.map((item) => (
-
+
+ {activeItems.map((item) => (
+
{item.name}
-
+
))}
-
-
+
+
);
}
diff --git a/.claude/skills/hugagent-frontend-dev/templates/css-module.css b/.claude/skills/hugagent-frontend-dev/templates/css-module.css
index a1c2e9b8..6701c6c8 100644
--- a/.claude/skills/hugagent-frontend-dev/templates/css-module.css
+++ b/.claude/skills/hugagent-frontend-dev/templates/css-module.css
@@ -1,96 +1,126 @@
/**
- * CSS module template.
+ * CSS template (UI design spec v2).
*
- * Replace ${componentName} with actual name.
- * Add to styles/ or alongside the component.
- * Import in styles/index.ts or component file.
+ * Replace ${componentName} with the actual name. Put it in styles/ (or a styles/ / subfolder) and import it
+ * from styles/index.ts, or import it from the component file.
*
- * Rules:
- * - All classes prefixed with .jx-
- * - Colors use CSS variables (--color-*), see references/ui-design-spec.md
- * - BEM-like: .jx-name (block), .jx-name-child (element), .jx-name--mod (modifier)
- * - Spacing: 4px grid (--space-xs/sm/md/lg/xl)
- * - Border-radius: --radius-xs(4) / --radius-sm(8) / --radius-md(12) / --radius-lg(20)
- * - Font size: 12/14(default)/16/18/22/44px
+ * Rules — see references/ui-design-spec.md and references/css-variables.md:
+ * - Classes prefixed .jx- ; BEM-like: .jx-name / .jx-name-child / .jx-name--mod
+ * - Colors only through tokens (npm run check:dark fails on literals, rgba(), and var(--x, #fff) fallbacks)
+ * - 8-point grid: 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64. Do not use --space-lg (20) / --space-xl (40) in new code
+ * - Concentric radii: inner radius = outer radius − padding. --radius-xs 6 / sm 10 / md 16 / lg 24 / full 999px
+ * - Resting cards have no shadow; shadow only on hover (raised) and on floating layers
+ * - Hover/press = overlay that darkens, never a lighter color; disabled = opacity .4
+ * - Motion: only opacity/transform, tokens for duration/easing, never `ease` / `ease-out` / new cubic-bezier
+ * - Font weights 400 / 500 / 600 only; sizes in rem
+ *
+ * v2 tokens marked (v2) may not be in variables.css yet: grep first; if missing, add them to BOTH the light
+ * :root block and the dark block (values from assets/design-v2/tokens/hugagentos-tokens.v2.css) before use.
*/
-/* --- Container --- */
+/* --- Block --- */
.jx-${componentName} {
display: flex;
flex-direction: column;
- gap: var(--space-md); /* 16px */
- padding: var(--space-lg); /* 20px */
+ gap: var(--space-md); /* 16 */
+ padding: 24px; /* --space-6 (v2) */
+ color: var(--color-text);
}
-/* --- Header --- */
+/* --- Header: hierarchy from size/weight/spacing, not boxes --- */
.jx-${componentName}-header {
display: flex;
align-items: center;
justify-content: space-between;
+ gap: 12px; /* --space-3 (v2) */
}
-.jx-${componentName}-header h3 {
+.jx-${componentName}-title {
margin: 0;
- font-size: 16px;
+ font-size: 1.125rem; /* title-3: --font-size-title-3 (v2) */
+ line-height: 1.625rem;
font-weight: 600;
- color: var(--color-text);
}
/* --- List --- */
.jx-${componentName}-list {
display: flex;
flex-direction: column;
- gap: var(--space-sm); /* 8px */
+ gap: var(--space-sm); /* 8 */
+ margin: 0;
+ padding: 0;
+ list-style: none;
}
-/* --- Item --- */
+/* --- L1 item: resting = no border, no shadow; grouped by background + spacing --- */
.jx-${componentName}-item {
display: flex;
align-items: center;
- gap: var(--space-md); /* 16px */
- padding: var(--space-md); /* 16px */
- border-radius: var(--radius-md); /* 12px - card level */
- border: 1px solid var(--color-border);
- background: #fff;
- transition: box-shadow 0.2s, border-color 0.2s;
+ gap: 12px;
+ min-height: 32px; /* default control height; touch targets ≥ 44 */
+ padding: 12px var(--space-md);
+ border-radius: var(--radius-md);
+ background: var(--color-bg-container);
+ font-size: 0.875rem; /* body */
+ line-height: 1.375rem;
cursor: pointer;
+ transition:
+ transform var(--motion-duration-fast) var(--motion-ease-standard),
+ box-shadow var(--motion-duration-fast) var(--motion-ease-standard);
}
.jx-${componentName}-item:hover {
- border-color: var(--color-primary-hover);
- box-shadow: 0 4px 12px rgba(18, 109, 255, 0.08);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-raised); /* (v2) — until merged: var(--shadow-card-hover) */
}
-/* --- Item active modifier --- */
+.jx-${componentName}-item:active {
+ transform: scale(.97);
+}
+
+/* Selected: primary light background + primary text; no extra border */
.jx-${componentName}-item--active {
- border-color: var(--color-primary);
background: var(--color-primary-light);
+ color: var(--color-primary-text); /* (v2) — until merged: var(--color-primary) */
}
-/* --- Item disabled modifier --- */
.jx-${componentName}-item--disabled {
- opacity: 0.5;
+ opacity: .4;
cursor: not-allowed;
}
-/* --- Empty state --- */
+/* Secondary metadata: footnote/caption + tertiary text; numbers tabular */
+.jx-${componentName}-meta {
+ font-size: 0.8125rem; /* footnote */
+ color: var(--color-text-tertiary);
+ font-variant-numeric: tabular-nums;
+}
+
+/* Semantic hint: light background + *-text foreground, never the fill color as text */
+.jx-${componentName}-hint--error {
+ padding: var(--space-sm) 12px;
+ border-radius: var(--radius-sm);
+ background: var(--color-error-bg); /* v2 value = v1 --color-error-light */
+ color: var(--color-error-text); /* (v2) */
+}
+
+/* --- Empty state: one plain sentence, no illustration box --- */
.jx-${componentName}-empty {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 0;
+ padding: 48px 0; /* --space-12 (v2) */
+ text-align: center;
+ font-size: 0.875rem;
color: var(--color-text-tertiary);
- font-size: 14px;
}
-/* --- Responsive --- */
+/* --- Entrance: reuse motion primitives (.jx-anim-fadeInUp etc.) instead of new keyframes --- */
+
+/* --- Narrow container / mobile --- */
@media (max-width: 768px) {
.jx-${componentName} {
- padding: var(--space-md); /* 16px */
+ padding: var(--space-md); /* 16 + safe area where relevant */
}
.jx-${componentName}-item {
- padding: 10px var(--space-md);
+ min-height: 44px;
}
}
diff --git a/.claude/skills/hugagent-frontend-dev/templates/hook.ts b/.claude/skills/hugagent-frontend-dev/templates/hook.ts
index 9b09e939..284e8301 100644
--- a/.claude/skills/hugagent-frontend-dev/templates/hook.ts
+++ b/.claude/skills/hugagent-frontend-dev/templates/hook.ts
@@ -2,87 +2,35 @@
* Custom hook template.
*
* Replace ${Feature}, ${feature} with actual names.
- * Create as hooks/use${Feature}.ts
+ * Create as hooks/use${Feature}.ts. hooks/index.ts only re-exports the most common hooks;
+ * import others by deep path.
+ *
+ * Rules:
+ * - Network calls go through API functions (api.ts / src/api/.ts), which use apiRequest()
+ * - Every request started by the hook is aborted on unmount or when superseded
+ * - Effects are split by concern; no conditional hooks
*/
-import { useState, useEffect, useCallback, useRef } from 'react';
-// import { use${Feature}Store } from '../stores';
-// import { authFetch } from '../api';
-
-// ---------------------------------------------------------------------------
-// Hook
-// ---------------------------------------------------------------------------
-
-export function use${Feature}(apiUrl: string) {
- // --- Store state ---
- // const { items, setItems, loading, setLoading } = use${Feature}Store();
+import { useCallback, useEffect, useRef } from 'react';
+import { use${Feature}Store } from '../stores';
- // --- Local state ---
- const [initialized, setInitialized] = useState(false);
-
- // --- Refs ---
+export function use${Feature}() {
+ const fetchItems = use${Feature}Store((s) => s.fetchItems);
+ const loading = use${Feature}Store((s) => s.loading);
const abortRef = useRef(null);
- const timerRef = useRef | null>(null);
-
- // --- Initialization ---
- useEffect(() => {
- if (initialized) return;
- const init = async () => {
- try {
- // const r = await authFetch(`${apiUrl}/v1/${feature}s`);
- // const { data } = await r.json();
- // setItems(data.items);
- setInitialized(true);
- } catch (e) {
- console.error('Failed to init ${feature}:', e);
- }
- };
-
- init();
- }, [apiUrl, initialized]);
-
- // --- Cleanup ---
- useEffect(() => {
- return () => {
- abortRef.current?.abort();
- if (timerRef.current) clearTimeout(timerRef.current);
- };
- }, []);
-
- // --- Actions ---
const refresh = useCallback(async () => {
- // setLoading(true);
- try {
- // const r = await authFetch(`${apiUrl}/v1/${feature}s`);
- // const { data } = await r.json();
- // setItems(data.items);
- } catch (e) {
- console.error('Refresh failed:', e);
- } finally {
- // setLoading(false);
- }
- }, [apiUrl]);
+ abortRef.current?.abort();
+ const controller = new AbortController();
+ abortRef.current = controller;
+ await fetchItems(controller.signal);
+ }, [fetchItems]);
- const doSomething = useCallback(async (id: string) => {
- abortRef.current = new AbortController();
- try {
- // await authFetch(`${apiUrl}/v1/${feature}s/${id}/action`, {
- // method: 'POST',
- // signal: abortRef.current.signal,
- // });
- } catch (e) {
- if ((e as Error).name !== 'AbortError') {
- console.error('Action failed:', e);
- }
- }
- }, [apiUrl]);
+ // Initial load + cleanup.
+ useEffect(() => {
+ void refresh();
+ return () => abortRef.current?.abort();
+ }, [refresh]);
- // --- Return ---
- return {
- initialized,
- refresh,
- doSomething,
- abortRef,
- };
+ return { loading, refresh };
}
diff --git a/.claude/skills/hugagent-frontend-dev/templates/store.ts b/.claude/skills/hugagent-frontend-dev/templates/store.ts
index e4fd65a6..6b6defa7 100644
--- a/.claude/skills/hugagent-frontend-dev/templates/store.ts
+++ b/.claude/skills/hugagent-frontend-dev/templates/store.ts
@@ -2,130 +2,101 @@
* Zustand store template.
*
* Replace ${Feature}, ${feature} with actual names.
- * Create as stores/${feature}Store.ts
+ * Create as stores/${feature}Store.ts and re-export it from stores/index.ts.
+ *
+ * Rules:
+ * - Server data comes from API functions (api.ts or src/api/.ts), never raw fetch here
+ * - Business data cached in localStorage MUST be keyed per account with userScopedKey(base, userId)
+ * (a null key means "not signed in yet": do not read or write)
+ * - Clear account-scoped state on logout / account switch (reset())
*/
import { create } from 'zustand';
-
-// ---------------------------------------------------------------------------
-// Types
-// ---------------------------------------------------------------------------
+import { loadJsonPref, removeLocal, saveJsonPref, userScopedKey } from '../storage';
+// import { list${Feature}s, create${Feature}, delete${Feature} } from '../api/${feature}';
interface ${Feature}Item {
id: string;
name: string;
- // ... add fields
}
-// ---------------------------------------------------------------------------
-// State Interface (state + actions)
-// ---------------------------------------------------------------------------
-
interface ${Feature}State {
- // --- State ---
+ userId: string | null;
items: ${Feature}Item[];
loading: boolean;
selectedId: string | null;
error: string | null;
- // --- Actions ---
- setItems: (items: ${Feature}Item[]) => void;
- setLoading: (loading: boolean) => void;
+ bindUser: (userId: string | null) => void;
selectItem: (id: string | null) => void;
- fetchItems: () => Promise;
- createItem: (data: Partial<${Feature}Item>) => Promise;
+ fetchItems: (signal?: AbortSignal) => Promise;
+ createItem: (name: string) => Promise<${Feature}Item>;
deleteItem: (id: string) => Promise;
reset: () => void;
}
-// ---------------------------------------------------------------------------
-// localStorage persistence (optional)
-// ---------------------------------------------------------------------------
-
-const STORAGE_KEY = 'hugagent_ui_${feature}_v1';
+const STORAGE_BASE = 'hugagent_ui_${feature}_v1';
-function loadFromStorage(): ${Feature}Item[] {
- try {
- const raw = localStorage.getItem(STORAGE_KEY);
- return raw ? JSON.parse(raw) : [];
- } catch {
- return [];
- }
+function persist(userId: string | null, items: ${Feature}Item[]) {
+ const key = userScopedKey(STORAGE_BASE, userId);
+ if (key) saveJsonPref(key, { items });
}
-function saveToStorage(items: ${Feature}Item[]) {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
-}
-
-// ---------------------------------------------------------------------------
-// Store
-// ---------------------------------------------------------------------------
-
export const use${Feature}Store = create<${Feature}State>((set, get) => ({
- // --- Initial state ---
- items: loadFromStorage(),
+ userId: null,
+ items: [],
loading: false,
selectedId: null,
error: null,
- // --- Simple setters ---
- setItems: (items) => {
- set({ items });
- saveToStorage(items);
+ bindUser: (userId) => {
+ const key = userScopedKey(STORAGE_BASE, userId);
+ const cached = key ? loadJsonPref<{ items: ${Feature}Item[] }>(key, { items: [] }) : { items: [] };
+ set({ userId, items: cached.items, selectedId: null, error: null });
},
- setLoading: (loading) => set({ loading }),
+
selectItem: (id) => set({ selectedId: id }),
- // --- Async: fetch ---
- fetchItems: async () => {
+ fetchItems: async (signal) => {
set({ loading: true, error: null });
try {
- // const r = await authFetch(`${apiUrl}/v1/${feature}s`);
- // const { data } = await r.json();
- // set({ items: data.items });
- // saveToStorage(data.items);
+ const items: ${Feature}Item[] = []; // = await list${Feature}s(signal);
+ void signal;
+ set({ items });
+ persist(get().userId, items);
} catch (e) {
+ if ((e as Error).name === 'AbortError') return;
set({ error: (e as Error).message });
- console.error('Failed to fetch ${feature}s:', e);
} finally {
set({ loading: false });
}
},
- // --- Async: create ---
- createItem: async (data) => {
- try {
- // const r = await authFetch(`${apiUrl}/v1/${feature}s`, {
- // method: 'POST',
- // headers: { 'Content-Type': 'application/json' },
- // body: JSON.stringify(data),
- // });
- // const { data: created } = await r.json();
- // const items = [...get().items, created];
- // set({ items });
- // saveToStorage(items);
- } catch (e) {
- console.error('Failed to create ${feature}:', e);
- throw e;
- }
+ createItem: async (name) => {
+ const created: ${Feature}Item = { id: '', name }; // = await create${Feature}(name);
+ const items = [created, ...get().items];
+ set({ items });
+ persist(get().userId, items);
+ return created;
},
- // --- Async: delete ---
+ // Optimistic delete with rollback: the list stays truthful if the request fails.
deleteItem: async (id) => {
+ const previous = get().items;
+ const items = previous.filter((item) => item.id !== id);
+ set({ items, selectedId: get().selectedId === id ? null : get().selectedId });
try {
- // await authFetch(`${apiUrl}/v1/${feature}s/${id}`, { method: 'DELETE' });
- const items = get().items.filter((i) => i.id !== id);
- set({ items, selectedId: get().selectedId === id ? null : get().selectedId });
- saveToStorage(items);
+ // await delete${Feature}(id);
+ persist(get().userId, items);
} catch (e) {
- console.error('Failed to delete ${feature}:', e);
+ set({ items: previous });
throw e;
}
},
- // --- Reset ---
reset: () => {
- set({ items: [], loading: false, selectedId: null, error: null });
- localStorage.removeItem(STORAGE_KEY);
+ const key = userScopedKey(STORAGE_BASE, get().userId);
+ if (key) removeLocal(key);
+ set({ userId: null, items: [], loading: false, selectedId: null, error: null });
},
}));
diff --git a/.env.example b/.env.example
index 68b1ad2c..1fed3dc3 100644
--- a/.env.example
+++ b/.env.example
@@ -31,17 +31,18 @@ VITE_DEFAULT_LANGUAGE=en
# ── LLM(模型接入) ──────────────────────────────────────────────────────────
# 模型配置存数据库(不走 env):启动后在 Web「设置 → 系统管理 → 模型服务」
# 填 OpenAI 兼容端点(base_url / api_key / 模型名),或本地单机模式跑
-# `hugagent onboard` 交互引导。
+# `hugagent-server onboard` 交互引导。
# ── 互联网搜索(可选;也可在「设置 → 系统管理 → 服务配置」里配,DB 优先) ──
-# INTERNET_SEARCH_ENGINE=tavily # tavily / baidu / langsearch
+# INTERNET_SEARCH_ENGINE=tavily # tavily / baidu / langsearch / deepseek
# TAVILY_API_KEY=
# BAIDU_API_KEY=
# LANGSEARCH_API_KEY=
+# DEEPSEEK_SEARCH_API_KEY=
# ── 存储(社区版:本地文件) ─────────────────────────────────────────────────
STORAGE_TYPE=s3
-COMPOSE_FILE=docker-compose.yml:docker/compose.object-storage.yml
+COMPOSE_FILE=docker-compose.yml:docker/object-storage/compose.object-storage.yml
S3_ENDPOINT=http://object-storage:9000
S3_BUCKET=hugagent-files
S3_REGION=us-east-1
@@ -86,10 +87,10 @@ REDIS_HOST_PORT=6380
# Personal Agent (CE/EE): optional module, off unless enabled here.
# CE allows authenticated users; EE also applies account/role/team permission.
# No Personal Agent license feature is required. Start the dedicated
-# controller with docker/compose.personal-agent.yml and --profile personal-agent.
+# controller with docker/personal-agent/compose.personal-agent.yml and --profile personal-agent.
PERSONAL_AGENT_ENABLED=false
# Build a versioned graphical image before use.
-# docker build -f docker/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .
+# docker build -f docker/personal-agent/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .
# Do not overwrite a tag used by a running permanent computer.
PERSONAL_AGENT_IMAGE=hugagent-personal-agent:v5
# Backend API reachable from the permanent computer. No credential value here.
diff --git a/.gitignore b/.gitignore
index f99ef493..ac636bd6 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
# Runtime-generated catalog (synced from DB + filesystem at startup)
src/backend/configs/catalog.json
-# OpenSandbox config — generated from docker/opensandbox-config.toml.tpl by the
+# OpenSandbox config — generated from docker/opensandbox-server/opensandbox-config.toml.tpl by the
# opensandbox-config-init service at compose-up (sed 把 @@HOST_REPO_PATH@@
# 替换成各机 .env 里的实际仓库路径)。.tpl 入库,实文件不入库。
opensandbox-config.toml
@@ -60,6 +60,8 @@ spike/
.DS_Store
# Project specific
+# 根目录 examples/(示例工程)仅本地使用,不入库
+/examples/
output/
images/
logs/
@@ -164,10 +166,12 @@ deploy_kit/
node_modules/
# 建站模板必须完整入库(覆盖上面的 lib/ 与 AGENTS.md 规则),但依赖树除外
-!docker/site-template/**
-docker/site-template/react-vite/node_modules/
+!docker/sandbox/site-template/**
+docker/sandbox/site-template/react-vite/node_modules/
.jobtest/
# Local scratch scripts and verification artifacts
/.scratch/
+# macOS native release archives (desktop/scripts/workbench-macos-native.mjs)
+/release-archive/
.ageval
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 365c5fa5..f3ae4788 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
.PHONY: help install install-dev dev test selftest lint format type-check security-scan \
- migrate migrate-new migrate-down migrate-history db-reset db-seed \
+ migrate migrate-new migrate-down migrate-history db-reset \
build build-no-cache up up-recreate down down-volumes restart logs logs-backend logs-frontend ps \
health-check clean clean-docker clean-all
@@ -32,7 +32,6 @@ help:
@echo " make migrate-down - Rollback one migration"
@echo " make migrate-history - Show migration history"
@echo " make db-reset - Reset DB to latest (destructive)"
- @echo " make db-seed - Seed sample data"
@echo ""
@echo "Docker:"
@echo " make build - Build images"
@@ -105,9 +104,6 @@ db-reset:
alembic downgrade base
alembic upgrade head
-db-seed:
- PYTHONPATH=$(PYTHONPATH_BACKEND) $(PYTHON) src/backend/scripts/seed_data.py
-
build:
$(COMPOSE) build
diff --git a/README.md b/README.md
index c359056f..d95b57cd 100644
--- a/README.md
+++ b/README.md
@@ -3,16 +3,12 @@
- HugAgentOS: The Enterprise AgentOS for Ontology-Grounded Trustworthy Reasoning
+ HugAgentOS: the open-source Agent Harness that runs 24/7
- The open-source, self-hosted foundation for enterprise AI agents
-
-
-
- Give models the context and tools to retrieve knowledge, work with files,
- run code, and carry real tasks through to completion.
+ Hand it your question, then close your laptop —
+ the agent keeps working in the cloud and tells you when it's done.
@@ -37,8 +33,11 @@
-
-
+
+
+
+
+
@@ -48,11 +47,18 @@
-HugAgentOS is an enterprise-grade AgentOS that treats domain ontology as a
-control plane for agent reasoning, decisions, and actions. Its open-source
-Community Edition combines agentic chat, private knowledge-base RAG,
-sub-agents, MCP tools, Agent Skills, sandboxed execution, long-term memory,
-automation, and a data canvas in one self-hosted workspace.
+Models keep getting stronger, but getting an agent to **actually finish the job**
+depends on the layer around the model: run lifecycle, context assembly, tools and
+sandboxes, human approvals, memory, and failure recovery. That layer is the
+**agent harness**.
+
+HugAgentOS is an open-source, self-hosted agent harness built around the
+**durable agent**: your request runs **in the cloud**, not in your browser or on your
+computer. Close the tab, shut the laptop or go offline, and the task keeps moving;
+you can also hand off tasks and get results in the IM app you use. On top of
+that, HugAgentOS provides an always-on **Personal Agent** and runs in two forms —
+**HugAgent Cloud** (on your server) and **HugAgent Local** (on your desktop) — which
+can also be combined.
> [!NOTE]
> This Community repository is generated from the upstream main repository for
@@ -60,6 +66,18 @@ automation, and a data canvas in one self-hosted workspace.
> Issue or Discussion. Pull requests for documentation and examples are
> welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
+## At a glance
+
+| Situation | A typical chat assistant | HugAgentOS |
+|---|---|---|
+| You close your laptop | The task stops with it | The task keeps running in the cloud; come back to the result |
+| The server restarts or fails | In-flight work is lost | Another instance takes over from the last saved progress |
+| A step fails and retries | It may send the email twice | Operations with an uncertain outcome are never redone automatically |
+| A very long task | Bound by timeouts and turn limits | No business time limit; it runs until the job is done |
+| Reaching you | Waits for you to open the page | IM notifications, schedules and heartbeat reminders |
+| Where it runs | Cloud or local, pick one | HugAgent Cloud, HugAgent Local, or a local + cloud hybrid |
+| Over time | Starts from zero every time | Memory, procedures and skills accumulate, each approved and reversible |
+
## Demo
A 60-second tour: the same task run twice, with audited self-evolution in between —
@@ -68,140 +86,190 @@ approve it.
https://github.com/user-attachments/assets/635c5b35-10c0-47b9-a6f1-c73df9510e90
-## Quick start
+## ⏱️ Durable agent: close the laptop, the work goes on
-Use the one-command install to try it out, or Docker Compose for a long-running
-service with isolation. Either way you need an OpenAI-compatible or local model.
-**The initial account and password are both `admin` and must be changed on first
-sign-in; the Community Edition has no self-registration.**
+Once you hand a task to HugAgentOS, it runs in cloud services and sandboxes — **your
+device is just a window onto it**. Leave whenever you like, or come back on another
+device; you always see the same progress and results.
-### Option 1: one-command install (Linux / macOS / WSL2)
-
-Requires Python 3.11+, Node.js 20+, Git and `curl`. No Docker, PostgreSQL or
-Redis needed.
-
-```bash
-curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash
+```text
+09:00 In Feishu, tell the agent: "Research pricing for competitors A, B and C, put it in a table and write a comparison report"
+09:01 The agent replies "Started"; the task runs in a cloud sandbox → you close the laptop and go to a meeting
+10:40 It needs to send an email: an approval reminder arrives in Feishu, and you approve it on the web from your phone
+11:30 The report arrives in the same Feishu chat
+11:35 Reply "Add D as well" → the same task picks it up, no restart
```
-The installer fetches the source into `~/.hugagent/source`, creates an isolated
-Python environment, builds the web application and walks you through first-run
-setup; it then opens [http://127.0.0.1:3001](http://127.0.0.1:3001). Start it
-again later with `~/.hugagent/venv/bin/hugagent`.
+**Why it keeps going**
+
+- **Runs in the cloud, not on your device**: background tasks, schedules and heartbeats
+ run on the server, and working files live in a dedicated persistent sandbox. Closing
+ the page doesn't stop anything, and any device can pick up the live progress.
+- **Survives server failures**: progress is saved to the database step by step and
+ executed by leased workers. If a process or machine dies, another instance takes over
+ from the last saved progress instead of silently dropping the task.
+- **Never repeats risky actions**: emails, writes to external systems and other
+ operations record their intent and outcome. On recovery, anything that may or may not
+ have happened is not redone automatically — you confirm "executed" or "not executed".
+- **Runs until it's done**: long tasks and the
+ [autonomous loop](./document/en/modules/autonomous-loop.md) have no total time limit or
+ business turn cap; stall detection, failure circuit breakers and a hard backstop prevent
+ runaways. Waiting for your approval or answer doesn't consume the execution budget.
+- **Change your mind mid-run**: add requirements at any time and the agent takes them at
+ its next step, with no cancel-and-restart.
+- **Finds you when it needs you**: actions that need approval are approved on the web, with
+ a reminder in IM; until you answer, the task waits instead of acting on its own.
+
+> [!IMPORTANT]
+> Continuous cloud operation relies on PostgreSQL, Redis and OpenSandbox — deploy
+> **HugAgent Cloud** with Docker Compose. The one-command install uses SQLite and
+> in-process state and is meant for trials and development.
+
+## 🤖 Personal Agent: an agent that is always on
+
+Personal Agent is built on the durable harness above: **one conversation, one
+persistent identity, one permanent cloud computer**. You just say what you need; it
+decides whether to answer right away or start a background task, and reports back
+when the work is done.
+
+| Capability | Description |
+|---|---|
+| 💬 **One entry, parallel background tasks** | Small talk and status questions are answered on the spot; research, reports, code and computer work become background tasks (3 in parallel by default, adjustable from 1 to 8). Follow-ups go in the same chat and it decides which task they belong to |
+| 🖥️ **Permanent sandbox and cloud computer** | Each user gets a dedicated OpenSandbox with no expiry, with separate persistent volumes for the workspace and browser profile; a lost container is rebuilt on the same volumes |
+| ⏰ **Schedules and heartbeats** | "Remind me to check email at 8 every morning" creates a schedule; heartbeats check `HEARTBEAT.md` at an interval and only speak up when something needs you |
+| 📱 **Reachable on your channels** | Bind Feishu, DingTalk, WeChat or WeCom bots; each contact or group gets its own session and results go back where the request came from |
+| ✅ **Execution you control** | Email, computer input and session writes need approval; every email is approved individually, other actions can be allowed for the rest of a task; a rejection makes it find another way |
+| 🧠 **Persona and memory as files** | `SOUL.md`, `USER.md`, `MEMORY.md` and daily logs live in the workspace and can be viewed and edited in My Space; Dreaming periodically consolidates logs into long-term memory |
+| 💻 **Connect your own computer** | In the desktop client's local + cloud mode, connect a Mac, Windows or Linux machine in one click and grant screen control or local session management; the device only polls outbound and opens no inbound ports |
+
+Personal Agent is off by default in the Community Edition. In a HugAgent Cloud
+deployment, set `PERSONAL_AGENT_ENABLED=true` and enable the `personal-agent` Compose
+profile. See the [Personal Agent guide](./document/en/modules/personal-agent.md).
+
+## ☁️ HugAgent Cloud and 💻 HugAgent Local
+
+The same harness, placed wherever your data and compute should live.
+
+| | **HugAgent Cloud** | **HugAgent Local** | **Local + cloud (hybrid)** |
+|---|---|---|---|
+| Runs on | Your server or private cloud | Your computer | Cloud as control plane, local machine as execution plane |
+| Access | Browser, from any device | Desktop client (Windows / macOS / Linux) | Desktop client |
+| Data and state | PostgreSQL, Redis, S3-compatible storage | Local SQLite, Milvus Lite, local files | Cloud projects stay in the cloud; local projects stay on the machine |
+| Execution | Isolated OpenSandbox sandboxes with snapshots | Your real project directories, gated by folder grants and permission profiles | Both |
+| Best for | 24/7 Personal Agent, long runs, IM channels | Offline work, sensitive data, local code and files | An always-on cloud agent that can also operate your machine and projects |
+| Install with | Docker Compose | Desktop offline installer, or the one-command script | Choose "Local + cloud" in the desktop client |
+
+- **HugAgent Cloud** is the complete durable harness, for work that must stay online,
+ be reachable from any device and connect to IM channels. Each Docker sandbox uses a
+ separate bridge with the trusted backend and controller; sandbox host ports are not
+ published and peer sandboxes cannot reach each other's execd.
+- **HugAgent Local** ships its own private Python runtime and services, so **no Python,
+ Docker or database install is needed**. It can run fully offline with local models;
+ once a local project is bound, Bash and file tools work directly in the project's real
+ directory. A failed upgrade rolls back to the previous version and data backup.
+- **Hybrid mode** uses one cloud account: cloud conversations are stored in the cloud,
+ while only local projects' conversations, files and execution stay on the machine.
+ This is also how Personal Agent "connects this computer" so an always-on cloud agent
+ can operate your desktop.
+
+See [Desktop local mode](./document/en/deployment/desktop-local-mode.md) and
+[Local project workspace](./document/en/deployment/local-project-workspace.md).
+
+## 🧬 Self-evolution: better with real work, audited at every step
+
+HugAgentOS does not make the agent start from zero every time — and does not let it
+quietly learn the wrong things.
+
+- **Learning procedures from trajectories**: the post-response memory pipeline keeps
+ outcome-verified trajectories such as "failure → user correction → success" as
+ reusable **procedural** memory instead of facts that go stale; stable procedures can
+ be compiled further into skills.
+- **Three memory layers**: L1 personal profile, L2 vector procedural memory (Milvus,
+ optional) and L3 relationship graph (Neo4j, optional). Writes go through a durable
+ outbox so crash replays never add duplicates, and a sanitizer filters credentials.
+- **Approved one by one, reversible**: evolution proposals for memory and skills appear
+ as cards and only take effect after you confirm them; you can switch it off at any time.
+- **Dreaming for Personal Agent**: a daily job consolidates logs into `MEMORY.md` /
+ `USER.md`, with `DREAMS.md` recording sources, deduplication and conflicts; only what
+ you explicitly asked it to remember is promoted automatically.
+- **Traceable ontology evolution**: approvals, rejections, evidence and outcomes are
+ recorded and organized into versioned ontology proposals that apply after human review.
+
+## 🧰 More harness capabilities
+
+| Capability | Description |
+|---|---|
+| 🔁 **Autonomous loop** | Goals survive across turns until done; maker ≠ checker — a worker does the work, a read-only reviewer verifies the real output, and command-checkable requirements are verified by the driver itself |
+| 🗺️ **Plan Mode** | Plan first, then execute step by step with per-step MCP, skill and sub-agent bindings; resumable like any run |
+| 🤝 **Sub-agents** | Built-in explorer, worker and reviewer, plus your own sub-agents, routed automatically or by `@` mention |
+| 🔧 **MCP, Skills and plugins** | Built-in web search, page fetching, knowledge retrieval, charts and automation tools; an MCP marketplace, private MCP servers, Agent Skills and plugins |
+| 🧪 **Sandbox and artifacts** | Run code in OpenSandbox or a local subprocess sandbox, producing charts, reports, Office files, web pages and data canvases |
+| 📚 **Private knowledge-base RAG** | Document chunking, hybrid vector and keyword retrieval, optional reranking |
+| 🛡️ **Optional ontology control plane** | With a Domain Pack, deterministic rule checks run before tool execution and high-risk answers are reviewed by an independent reviewer or committee; switched off, the original flow is unchanged |
+| 🔌 **Replaceable models** | Connect cloud or local OpenAI-compatible models through one model service; reviewer, planner, memory and other roles can each use their own model |
-> [!WARNING]
-> The installation listens on `127.0.0.1` only. If you genuinely need remote
-> access, use `hugagent serve --host 0.0.0.0 --port 3001 --no-browser`, and set a
-> strong password, a firewall and HTTPS first. Do not expose it on an untrusted
-> network.
+## Quick start
-This path uses SQLite, in-process state and a local subprocess sandbox, which
-suits personal use and development. Options and troubleshooting are in the
-[one-command install guide](./document/en/deployment/quick-install.md).
+Every option needs an OpenAI-compatible or local model. **The initial account and
+password are both `admin` and must be changed on first sign-in; the Community Edition
+has no self-registration.**
-### Option 2: Docker Compose
+### HugAgent Cloud: Docker Compose (recommended for 24/7)
-Use this when you need PostgreSQL, Redis, an isolated sandbox and persistent
-volumes. Requires Git, Docker and Compose v2.
+Install Git, Docker, and Compose v2 first.
```bash
git clone https://github.com/ZJU-REAL/HugAgentOS.git
cd HugAgentOS
-python3 scripts/configure-ce-docker.py
-docker build -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+cp .env.example .env
+mkdir -p data/storage
docker compose build backend
docker compose up -d --build
```
-Docker CE uses OpenSandbox by default. Initialization creates private controller credentials and absolute host paths. It refuses to overwrite an existing .env. Local installation keeps subprocess execution.
-
-Open [http://localhost:3002](http://localhost:3002), then connect a model under
-**Settings → System → Model services**. Profiles, persistence and production
-configuration are covered in the
-[Docker Compose deployment guide](./document/en/deployment/docker-compose.md).
-
-## Why HugAgentOS
-
-The point is not another chat wrapper. It is putting the context, the execution
-capability, and the artifact management an agent needs to finish real work on a
-single path — and raising the domain ontology from a knowledge base to a
-**machine-executable control plane**, so that governed concepts, relations, rules
-and action contracts give the skill, memory and orchestration engines one shared
-business vocabulary.
-
-
-
-
- 🔌 Model-agnostic
- Connect cloud or local models through one model-service configuration,
- without locking the application to a single vendor.
-
-
- 🛠️ Actually does the work
- ReAct orchestrates MCP servers, skills and sandboxes, so the model can
- search, analyse, produce files and call external capabilities.
-
-
-
-
- 🧠 Knowledge and memory
- Private knowledge bases and layered memory supply long-term context across
- files and conversations.
-
-
- 🏠 Your data stays yours
- Application, database and file storage all run on your own infrastructure.
-
-
-
-
- 🛡️ Gated, trustworthy execution
- Candidate plans pass deterministic rule checks, risk-tiered evidence review
- and a gate. A violating action returns with the rule, the evidence and a
- correction — it is never waved through silently.
-
-
- 🔎 Traceable evolution
- Approvals, rejections, evidence and outcomes are all recorded, distilled
- into versioned ontology proposals that take effect only after human review,
- and can be rolled back.
-
-
-
+Open [http://localhost:3002](http://localhost:3002), sign in, and connect a model
+under **Settings → System administration → Model services**. See the
+[Docker Compose guide](./document/en/deployment/docker-compose.md) for persistence
+and production settings, and [Deploy Personal Agent](./document/en/modules/personal-agent.md#deploy)
+to enable it.
-> [!NOTE]
-> The ontology trust control plane is an enterprise target architecture being
-> integrated into the existing harness in stages. It strengthens structured
-> compliance and evidence-based review; it does not promise "zero hallucination"
-> for free text.
+### HugAgent Local: desktop client
-## Core capabilities
+Download the installer for your platform from
+[Releases](https://github.com/ZJU-REAL/HugAgentOS/releases) and choose "Local only",
+"Cloud only" or "Local + cloud" on first launch. The full offline package bundles its
+runtime, so nothing else needs to be installed.
-The Community Edition covers the full loop for a personal agent — conversation,
-execution, consolidation and reuse. Optional components are enabled as needed.
+### Developer trial: one-command install (Linux / macOS / WSL2)
-| Capability | What it gives you |
-|---|---|
-| 💬 **Conversation and plan mode** | SSE streaming, ReAct tool orchestration, deep thinking, plan mode, citations, resumable runs |
-| 📚 **Private knowledge base RAG** | Document chunking, hybrid vector and keyword retrieval, optional reranking, per-user isolation |
-| 🤝 **Personal sub-agents** | Sub-agents with distinct roles, reached by automatic routing or an `@` mention |
-| 🔧 **MCP tool ecosystem** | Built-in web search, page fetch, knowledge retrieval, batch runs, automation, skill management |
-| 🧩 **Agent Skills** | Extend the agent with standardised skill definitions and scripts — built-in, marketplace and personal |
-| ⚙️ **Automation and batch runs** | Create scheduled tasks in natural language; run one process across an Excel sheet, a Word file or a file list |
-| 💬 **Group chat channels** | Feishu / DingTalk / WeCom bots, with optional group listening and history retrieval so the agent can see the conversation around it |
-| 🧪 **Sandbox and artifacts** | Run code locally or in an OpenSandbox container, producing charts, reports, Office files, web pages and data canvases |
-| 🧠 **Three-layer personal memory** | L1 profile in the relational store; optional Milvus vector memory and Neo4j graph memory |
-| 🧬 **Personal evolution** | Settle memory and skills out of your real work, each approved individually before it applies to you, and switchable off |
-| 🗂️ **Personal workspace** | Projects, folders, favourites, conversation sharing and an artifact center |
-| 📊 **Data canvas** | Inspect and edit structured data inside the conversation, keeping analysis and result in one workspace |
+Requires Python 3.11+, Node.js 20+, Git and `curl`. No Docker, PostgreSQL or
+Redis needed.
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash
+```
+
+The installer clones the source into `~/.hugagent/source`, creates an isolated
+Python environment, builds the web app and runs the first-time setup wizard, then
+opens [http://127.0.0.1:3001](http://127.0.0.1:3001). Start it again later with
+`~/.hugagent/venv/bin/hugagent-server`.
+
+> [!WARNING]
+> It listens on `127.0.0.1` only by default. If you really need remote access, use
+> `hugagent-server serve --host 0.0.0.0 --port 3001 --no-browser`, and set up a strong
+> password, a firewall and HTTPS first. Don't expose it directly on an untrusted network.
+
+The one-command install uses SQLite, in-process state and a local subprocess
+sandbox, which suits trials and development. See the
+[quick install guide](./document/en/deployment/quick-install.md) for options and
+troubleshooting.
## Architecture
-HugAgentOS separates user channels, agent workflows, reusable capability
-engines, ontology contracts, data governance, and infrastructure into clear
-layers. Action contracts connect the ontology layer to planning, validation,
-and gated execution, while security and platform governance span the complete
-stack.
+HugAgentOS separates user channels, agent workflows, reusable capability engines,
+ontology contracts, data governance, and infrastructure into clear layers. Durable
+runs, leases and recovery run through the execution path, while security and
+platform governance span the complete stack.

@@ -212,35 +280,35 @@ stack.
### Technology stack
-The project combines mature, replaceable open-source components behind clear
-service boundaries.
-
| Layer | Main technologies |
|---|---|
| Agent runtime | AgentScope 2.0, ReAct, Model Context Protocol |
| Backend | Python, FastAPI, SQLAlchemy, Alembic |
| Frontend | React 19, TypeScript, Vite, Zustand, Ant Design |
-| Data and state | SQLite or PostgreSQL 15, in-process state or Redis 7, local file storage |
+| Desktop | Tauri 2, private CPython 3.11 runtime |
+| Data and state | SQLite or PostgreSQL 15, in-process state or Redis 7, local or S3-compatible storage |
+| Sandbox | OpenSandbox, local subprocess |
| Optional memory | Milvus 2.4, Neo4j 5 Community, mem0 |
-| Deployment | One-command local installer, Docker Compose, Nginx |
+| Deployment | Desktop installer, one-command installer, Docker Compose, Nginx |
See the [architecture overview](./document/en/architecture/overview.md) for the
full request lifecycle, container topology, and design decisions.
## Community and Enterprise editions
-Community Edition gives an individual a complete agent workspace. Enterprise
-Edition adds the governance, collaboration, and delivery capabilities needed
-to operate the same experience across an organization. Enterprise-only source
-is physically absent from the Community tree.
+Community Edition lets an individual run the complete harness and Personal Agent.
+Enterprise Edition adds the governance, collaboration, and delivery capabilities
+needed to operate the same experience across an organization. Enterprise-only
+source is physically absent from the Community tree.
| Community Edition | Enterprise Edition adds |
|---|---|
-| Agentic chat, Plan Mode, and personal sub-agents | Teams, organization agents, and permission matrices |
-| 8 general MCP tools, personal skills, and a skill marketplace | Industry data tools, organization governance, and skill review |
-| Private knowledge bases and three-tier personal memory | Public knowledge administration and memory auditing |
+| Durable runs, autonomous loop, Plan Mode, and personal sub-agents | Teams, organization agents, and permission matrices |
+| Personal Agent: background tasks, dedicated computer, channels, schedules, and file memory | User, role, and team authorization, plus cross-device session management |
+| General MCP tools, personal skills, and a skill marketplace | Industry data tools, organization governance, and skill review |
+| Private knowledge bases, three-tier personal memory, and personal evolution | Public knowledge administration and memory auditing |
| Automation, and a personal data canvas | Organization billing, usage reports, and canvas collaboration |
-| OpenSandbox sessions, snapshots, and S3 file storage | Team workspace mounts, Cube, additional cloud providers, and offline delivery |
+| OpenSandbox sessions, snapshots, and S3 file storage; desktop local mode | Team workspace mounts, Cube, additional cloud providers, and offline delivery |
| Local accounts and branding with Powered-by attribution | SSO, compliance auditing, and full white-labeling |
See the [edition overview](./document/en/editions/overview.md) for the complete
@@ -248,26 +316,27 @@ feature boundary and upgrade path.
## Documentation
-The repository includes complete English and Chinese documentation for
-operators, users, and contributors, and you can read it offline.
-
| Goal | English | 中文文档 |
|---|---|---|
| Understand the product | [Introduction](./document/en/getting-started/introduction.md) | [产品简介](./document/zh-CN/getting-started/introduction.md) |
| Run it in 10 minutes | [Quick start](./document/en/getting-started/quick-start.md) | [快速开始](./document/zh-CN/getting-started/quick-start.md) |
+| Run lifecycle and resumable streams | [Chat](./document/en/modules/chat.md) | [智能对话](./document/zh-CN/modules/chat.md) |
+| The always-on personal agent | [Personal Agent](./document/en/modules/personal-agent.md) | [Personal Agent](./document/zh-CN/modules/personal-agent.md) |
+| Long-running self-driven tasks | [Autonomous loop](./document/en/modules/autonomous-loop.md) | [自主循环](./document/zh-CN/modules/autonomous-loop.md) |
+| Desktop local and hybrid modes | [Desktop local mode](./document/en/deployment/desktop-local-mode.md) | [桌面离线本机模式](./document/zh-CN/deployment/desktop-local-mode.md) |
+| Memory and self-evolution | [Memory](./document/en/modules/memory.md) | [记忆系统](./document/zh-CN/modules/memory.md) |
| Configure a deployment | [Deployment](./document/en/deployment/README.md) | [部署指南](./document/zh-CN/deployment/README.md) |
| Explore the system design | [Architecture](./document/en/architecture/overview.md) | [架构总览](./document/zh-CN/architecture/overview.md) |
-| Build a domain ontology | [Domain ontology quickstart](./document/en/getting-started/domain-ontology-quickstart.md) | [快速构建领域本体](./document/zh-CN/getting-started/domain-ontology-quickstart.md) |
-| Learn MCP, skills, memory, and sandboxing | [Modules](./document/en/README.md#modules) | [功能模块](./document/zh-CN/README.md#功能模块) |
+| The ontology control plane | [Domain ontology quickstart](./document/en/getting-started/domain-ontology-quickstart.md) | [快速构建领域本体](./document/zh-CN/getting-started/domain-ontology-quickstart.md) |
| Build backend or frontend features | [Development](./document/en/README.md#development) | [开发指南](./document/zh-CN/README.md#开发指南) |
Start from [document/README.md](./document/README.md) to browse every guide.
## Roadmap
-1. **Seamless cloud/local switching across clients** — one server deployment
- serving multiple clients, keeping conversations, agents, skills, files and task
- state in sync.
+1. **Seamless Cloud/Local switching across clients** — building on today's hybrid
+ mode, one server deployment serving multiple clients, keeping conversations,
+ agents, skills, files and task state in sync.
2. **Adaptive model routing based on Mixture of Agents** — select or combine models
by task complexity, modality, latency and cost: light models for simple work,
stronger ones only when the task warrants it.
@@ -299,5 +368,3 @@ supplementary terms. The terms restrict operating the software as a competing
multi-tenant SaaS offering and require the UI's Powered-by attribution to
remain visible. [LICENSE](./LICENSE) and [NOTICE](./NOTICE) define the complete
rights and obligations for internal use, modification, and distribution.
-
-Each Docker sandbox uses a separate bridge with the trusted backend and controller. Sandbox host ports are not published; peer sandboxes cannot access each other's execd. Use the bundled custom controller image.
diff --git a/README_CN.md b/README_CN.md
index 0502ea1b..3545dd22 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -3,16 +3,12 @@
- HugAgentOS:面向企业的本体驱动可信推理 AgentOS
+ HugAgentOS:7×24 小时持久运行的开源 Agent Harness
- 面向企业智能体的开源、自托管底座
-
-
-
- 让模型不只回答问题,还能检索知识、调用工具、处理文件、运行代码,
- 并持续完成真实任务。
+ 把问题交给它,然后合上电脑——
+ 智能体在云端继续干活,做完再告诉你。
@@ -37,8 +33,11 @@
-
-
+
+
+
+
+
@@ -48,16 +47,33 @@
-HugAgentOS 是面向企业级智能体场景的 AgentOS,把领域本体提升为推理、决策与
-行动的控制平面。其开源社区版将智能对话、私有知识库 RAG、子智能体、MCP 工具、
-Agent Skills、沙箱执行、长期记忆、自动化和数据画布整合到一个可私有部署的
-工作空间中。
+模型能力越来越强,但让智能体**真正把一件事干完**,靠的是模型之外的那一层:
+运行生命周期、上下文装配、工具与沙箱、人工审批、记忆、故障恢复。这一层就是
+**Agent Harness**。
+
+HugAgentOS 是一个开源、可自托管的 Agent Harness,核心是 **Durable Agent**:
+你提出的问题在**云端**执行,而不是依赖你的浏览器或电脑。关掉网页、合上笔记本、
+断网下班,任务都会继续推进;你也可以在常用的 IM 里下达任务、接收结果。在此之上,
+我们提供一个 7×24 小时在线的 **Personal Agent**,并以 **HugAgent Cloud**(服务器)
+和 **HugAgent Local**(桌面本机)两种形态运行,二者还可以混合使用。
> [!NOTE]
> 本社区仓库由上游主仓按发布版本自动生成,并标记为 `generated`。
> `src/**` 的修改请通过 Issue 或 Discussion 反馈;文档与示例欢迎直接提交 PR。
> 详细规则见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
+## 一眼看懂
+
+| 场景 | 普通对话式 AI | HugAgentOS |
+|---|---|---|
+| 你合上电脑 | 任务跟着停下 | 任务在云端继续,回来直接看结果 |
+| 服务器重启或宕机 | 进行中的任务丢失 | 其他实例接管,从上次保存的进度继续 |
+| 中途失败重试 | 可能重复发邮件、重复写入 | 不确定是否成功的操作不会自动重做 |
+| 很长的任务 | 受超时和轮数上限约束 | 不设业务时限,跑到做完为止 |
+| 怎么找到你 | 只能等你打开网页 | IM 推送、定时任务、心跳主动提醒 |
+| 在哪运行 | 云端或本地二选一 | HugAgent Cloud / HugAgent Local / 本机 + 云端混合 |
+| 用得越久 | 每次从零开始 | 记忆、做法与技能持续沉淀,逐条审批、可回滚 |
+
## 演示
60 秒速览:同一个任务做两次——中间是受审计的自进化,记忆、技能与编排逐条沉淀,
@@ -65,36 +81,115 @@ Agent Skills、沙箱执行、长期记忆、自动化和数据画布整合到
https://github.com/user-attachments/assets/a268e371-a11f-488b-98d4-08598e2fdf89
-## 快速开始
+## ⏱️ Durable Agent:合上电脑,任务继续
-个人试用用一键安装,长期服务或需要服务隔离用 Docker Compose。两者都需要准备
-OpenAI 兼容模型或本地模型。**初始账号与密码均为 `admin`,首次登录必须修改;
-CE 不提供自助注册。**
+把问题交给 HugAgentOS 后,任务运行在云端的服务和沙箱里,**你的设备只是一个入口**。
+你可以随时离开,也可以换一台设备回来,看到的始终是同一份进度和结果。
-### 方式一:一键安装(Linux / macOS / WSL2)
+```text
+09:00 在飞书里对智能体说:「调研竞品 A、B、C 的定价,整理成表格并写一份对比报告」
+09:01 智能体回复「已开始」,任务在云端沙箱里运行 → 你合上电脑去开会
+10:40 要发一封邮件?飞书里收到审批提醒,你在手机上打开网页点「批准」
+11:30 同一个飞书对话里收到汇报:报告完成
+11:35 接着回一句「再加上 D」→ 同一个任务继续做,不用从头来
+```
-需要 Python 3.11+、Node.js 20+、Git 和 `curl`;不需要 Docker、PostgreSQL 或 Redis。
+**为什么能一直跑下去**
-```bash
-curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash
-```
+- **执行在云端,不依赖你的设备**:后台任务、定时任务和心跳都在服务端运行,任务的工作文件
+ 保存在专属的持久沙箱中。关掉页面不会停止任务,任意设备打开都能接着看实时进度。
+- **服务器出问题也不丢**:任务进度按步写入数据库,并由带租约的 worker 执行。进程或机器中断后,
+ 其他实例接管,从最后保存的进度继续,而不是悄悄消失。
+- **不会重复做有风险的事**:发邮件、写入外部系统等操作都会记录意图与结果;恢复时,不确定是否
+ 已经执行的操作不会自动重做,而是请你确认「已执行 / 未执行」。
+- **跑到做完为止**:长任务和[自主循环](./document/zh-CN/modules/autonomous-loop.md)不设总时长和
+ 业务轮次上限;停滞检测、异常熔断和硬性后备防止失控。等你审批或回答期间,不消耗执行预算。
+- **中途可以改主意**:运行中随时补充要求,智能体在下一步采纳,不用取消重来。
+- **需要你时会找你**:需要审批的操作会在 IM 中提醒、在网页上批准;你没回复之前,任务等待而不是擅自行动。
-安装器会把源码拉到 `~/.hugagent/source`,创建隔离 Python 环境、构建 Web 应用并进入
-首次配置向导,完成后打开 [http://127.0.0.1:3001](http://127.0.0.1:3001)。之后用
-`~/.hugagent/venv/bin/hugagent` 再次启动。
+> [!IMPORTANT]
+> 云端持续运行依赖 PostgreSQL、Redis 与 OpenSandbox,请使用 **HugAgent Cloud**
+> (Docker Compose)部署。一键安装使用 SQLite 与进程内状态,适合个人试用与开发。
-> [!WARNING]
-> 默认仅监听 `127.0.0.1`。确需远程访问时用
-> `hugagent serve --host 0.0.0.0 --port 3001 --no-browser`,并先配置强密码、防火墙
-> 与 HTTPS,不要直接暴露在不受信任的网络中。
+## 🤖 Personal Agent:一个一直在线的个人智能体
-一键安装使用 SQLite、进程内状态与本地子进程沙箱,适合个人试用与开发;选项与故障
-排查见[一键安装指南](./document/zh-CN/deployment/quick-install.md)。
+Personal Agent 构建在上述 Durable Harness 之上:**一个对话入口、一个持续存在的身份、
+一台永久的云端电脑**。你只管说事,它决定是当场回答,还是开一个后台任务去做,
+做完再回来汇报。
+
+| 能力 | 说明 |
+|---|---|
+| 💬 **单一入口,并行后台任务** | 闲聊、查进度当场回答;调研、写报告、跑代码、操作电脑自动转为后台任务(默认并行 3 个,可调 1–8)。补充要求直接说,它自己判断归属哪个任务 |
+| 🖥️ **永久沙盒与云电脑** | 每个用户一台不设到期时间的专属 OpenSandbox,工作目录与浏览器资料独立持久卷;容器丢失时复用卷重建 |
+| ⏰ **定时任务与心跳** | 「每天 8 点提醒我看邮件」创建定时任务;心跳按间隔对照 `HEARTBEAT.md` 自查,无事不打扰,有事才提醒 |
+| 📱 **多渠道在线** | 绑定飞书、钉钉、微信、企业微信机器人;每个联系人或群拥有独立会话,结果发回来源会话 |
+| ✅ **可控的执行权** | 邮件、电脑输入、会话写入需要批准;邮件逐封批准,其他可「本任务内同类操作都允许」;拒绝后改用其他方法 |
+| 🧠 **文件化的人格与记忆** | 工作区里的 `SOUL.md`、`USER.md`、`MEMORY.md` 与每日日志,可在「我的空间」直接查看与编辑;Dreaming 定期把日志整理为长期记忆 |
+| 💻 **连接你的电脑** | 在桌面客户端「本机 + 云端」模式下一键连接 Mac / Windows / Linux,按授权接管屏幕或管理本机会话;设备只出站轮询,不开放入站端口 |
+
+Personal Agent 在社区版中默认关闭,在 HugAgent Cloud 部署中设置
+`PERSONAL_AGENT_ENABLED=true` 并启用 `personal-agent` Compose profile 后即可使用。
+完整说明见 [Personal Agent 文档](./document/zh-CN/modules/personal-agent.md)。
+
+## ☁️ HugAgent Cloud 与 💻 HugAgent Local
+
+同一套 Harness,按你的数据和算力放在哪里来选择运行形态。
+
+| | **HugAgent Cloud** | **HugAgent Local** | **本机 + 云端(混合)** |
+|---|---|---|---|
+| 运行位置 | 你的服务器 / 私有云 | 你的电脑 | 云端为控制面,本机为执行面 |
+| 入口 | 浏览器,多端访问 | 桌面客户端(Windows / macOS / Linux) | 桌面客户端 |
+| 数据与状态 | PostgreSQL、Redis、S3 兼容存储 | 本地 SQLite、Milvus Lite、本地文件 | 云端项目存云端,本地项目留本机 |
+| 执行环境 | OpenSandbox 隔离沙箱,快照恢复 | 本机真实项目目录,按目录授权与权限档执行 | 两者兼有 |
+| 适合 | 7×24 Personal Agent、长跑任务、多渠道接入 | 离线、敏感数据、本地代码与文件 | 云端常驻 + 操作本机电脑与项目 |
+| 部署方式 | Docker Compose | 桌面离线安装包;或一键安装脚本 | 桌面客户端选择「本机 + 云端」 |
+
+- **HugAgent Cloud**:完整的 Durable Harness。适合需要一直在线、跨设备访问、接入 IM 渠道的场景。
+ 每个 Docker 沙箱与受信任的后端和控制器使用独立网桥,不发布沙箱宿主端口,沙箱之间不能互访 execd。
+- **HugAgent Local**:桌面安装包自带私有 Python 运行时与服务,**不需要安装 Python、Docker
+ 或数据库**,可完全离线运行并接入本地模型;绑定本地项目后,Bash 与文件工具直接在项目真实
+ 目录中工作。升级失败自动回滚到上一版本与数据备份。
+- **混合模式**:一套云端账号,云端会话存云端;只有本地项目的对话、文件与执行留在本机。
+ Personal Agent 可以借此「连接这台电脑」,让云端常驻的智能体操作你的本机。
+
+详见[桌面离线本机模式](./document/zh-CN/deployment/desktop-local-mode.md)与
+[本机项目工作目录](./document/zh-CN/deployment/local-project-workspace.md)。
+
+## 🧬 自进化:从真实工作中变强,每一步都可审计
+
+HugAgentOS 不让智能体每次从零开始,也不让它「悄悄学坏」。
+
+- **从轨迹中学做法**:后置记忆流水线会识别「失败 → 用户纠正 → 再次成功」这类经过结果验证
+ 的轨迹,沉淀为可复用的**做法(procedural)**,而不是记住会过期的事实;稳定做法可以进一步
+ 编译为技能。
+- **三层记忆**:L1 个人画像、L2 向量做法记忆(Milvus,可选)、L3 关系图谱(Neo4j,可选);
+ 写入走持久 Outbox,崩溃重放不会重复新增,脱敏闸门过滤凭据。
+- **逐条审批、可回滚**:记忆与技能的进化建议以卡片形式出现,经你确认才生效,可随时关闭。
+- **Personal Agent 的 Dreaming**:每天定时把日志整理为 `MEMORY.md` / `USER.md` 的更新,
+ `DREAMS.md` 记录来源、去重与冲突;只自动提升你明确说「记住」的内容。
+- **可溯的本体进化**:审批、驳回、证据与执行结果留痕,整理为版本化的本体建议,人工审核后生效。
+
+## 🧰 更多 Harness 能力
+
+| 能力 | 说明 |
+|---|---|
+| 🔁 **自主循环** | 目标跨轮存活、做完为止;maker≠checker:worker 干活、只读评审员亲验产出,命令可判定的需求由 driver 机检 |
+| 🗺️ **计划模式** | 先出计划再逐步执行,步骤级绑定 MCP / 技能 / 子智能体,同样支持断线续播 |
+| 🤝 **子智能体** | 内置 explorer / worker / reviewer,可创建个人子智能体,自动路由或 `@` 提及协同 |
+| 🔧 **MCP · Skills · 插件** | 内置联网搜索、网页抓取、知识检索、图表、自动化等工具;MCP 市场、私有 MCP、Agent Skills 与插件 |
+| 🧪 **沙箱与产物** | OpenSandbox 或子进程沙箱执行代码,产出图表、报告、Office 文件、网页与数据画布 |
+| 📚 **私有知识库 RAG** | 文档分块,向量与关键词混合检索,可选重排 |
+| 🛡️ **可选的本体控制平面** | 导入 Domain Pack 后,工具执行前做确定性规则检查,高风险答案由独立评审员或委员会复核;关闭时完全不改变原流程 |
+| 🔌 **模型可替换** | 统一的模型服务配置接入云端或本地 OpenAI 兼容模型,评审、规划、记忆等角色可分别指定模型 |
-### 方式二:Docker Compose
+## 快速开始
-需要 PostgreSQL、Redis、隔离沙箱与持久化卷时使用。需先安装 Git、Docker 与
-Compose v2。
+所有方式都需要准备 OpenAI 兼容模型或本地模型。**初始账号与密码均为 `admin`,首次登录必须修改;
+CE 不提供自助注册。**
+
+### HugAgent Cloud:Docker Compose(推荐用于 7×24)
+
+需先安装 Git、Docker 与 Compose v2。
```bash
git clone https://github.com/ZJU-REAL/HugAgentOS.git
@@ -106,112 +201,75 @@ docker compose up -d --build
```
打开 [http://localhost:3002](http://localhost:3002),登录后进入「设置 → 系统管理 →
-模型服务」接入模型。Profiles、持久化与生产配置见
-[Docker Compose 部署指南](./document/zh-CN/deployment/docker-compose.md)。
-
-## 为什么是 HugAgentOS
-
-重点不是再包装一个聊天界面,而是把智能体完成任务所需的上下文、执行能力和产物管理
-放在同一条链路里;并把领域本体从「知识库」提升为**机器可执行的控制平面**——受控的
-概念、关系、铁律与 Action 契约,让技能、记忆和编排三大引擎共享同一套业务语言。
-
-
-
-
- 🔌 模型可替换
- 统一的模型服务配置接入云端或本地模型,不锁定单一供应商。
-
-
- 🛠️ 能执行任务
- ReAct 编排 MCP、技能与沙箱,让模型能搜索、分析、生成文件并调用外部能力。
-
-
-
-
- 🧠 有知识与记忆
- 私有知识库与分层记忆提供跨文件、跨会话的长期上下文。
-
-
- 🏠 数据可自持
- 应用、数据库与文件存储都可运行在自己的基础设施中。
-
-
-
-
- 🛡️ 门控可信执行
- 候选计划依次经过确定性规则检查、风险分级证据评审与门控执行;违规动作会带着
- 规则、证据和修正建议返回,不会静默放行。
-
-
- 🔎 可溯的持续进化
- 审批、驳回、证据与执行结果均留痕,整理为版本化本体建议,人工审核后生效并可回滚。
-
-
-
+模型服务」接入模型。启用 Personal Agent、持久化与生产配置见
+[Docker Compose 部署指南](./document/zh-CN/deployment/docker-compose.md)和
+[Personal Agent 部署](./document/zh-CN/modules/personal-agent.md#部署)。
-> [!NOTE]
-> 本体可信控制平面是正在现有 Harness 上分阶段集成的企业级目标架构。它强化结构化
-> 合规和基于证据的评审,但不对自由文本作「零幻觉」承诺。
+### HugAgent Local:桌面客户端
-## 核心能力
+从 [Releases](https://github.com/ZJU-REAL/HugAgentOS/releases) 下载对应平台的桌面安装包,
+首次启动选择「仅本机」「仅云端」或「本机 + 云端」。完整离线包自带运行时,无需额外安装依赖。
-社区版覆盖个人智能体从对话、执行到沉淀复用的完整闭环,可选组件按需启用。
+### 开发者试用:一键安装(Linux / macOS / WSL2)
-| 能力 | 说明 |
-|---|---|
-| 💬 **智能对话与计划模式** | SSE 流式、ReAct 工具编排、深度思考、计划模式、引用溯源、断线续播 |
-| 📚 **私有知识库 RAG** | 文档分块、向量与关键词混合检索、可选重排、个人知识库隔离 |
-| 🤝 **个人子智能体** | 创建不同角色的子智能体,自动路由或 `@` 提及协同 |
-| 🔧 **MCP 工具生态** | 内置联网搜索、网页抓取、知识检索、图表、报告、批量、自动化、技能管理 |
-| 🧩 **Agent Skills** | 标准化技能说明 + 脚本扩展,支持内置、市场与个人技能 |
-| ⚙️ **定时自动化** | 自然语言创建定时任务 |
-| 💬 **群聊接入** | 飞书 / 钉钉 / 企微机器人;可选群聊旁听与历史回溯,让智能体看得到群里的上下文 |
-| 🧪 **沙箱与产物** | 子进程或轻量容器沙箱执行代码,产出图表、报告、Office 文件、网页与数据画布 |
-| 🧠 **三层个人记忆** | L1 画像存关系库;可选 Milvus 向量记忆与 Neo4j 图谱记忆 |
-| 🧬 **个人进化** | 从你的真实工作沉淀记忆与技能,逐条审批后才对你生效,可随时关闭 |
-| 🗂️ **个人工作空间** | 项目、文件夹、收藏、会话分享与产物中心 |
-| 📊 **数据画布** | 会话内查看与编辑结构化数据,分析过程与结果同处一个工作区 |
+需要 Python 3.11+、Node.js 20+、Git 和 `curl`;不需要 Docker、PostgreSQL 或 Redis。
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash
+```
+
+安装器会把源码拉到 `~/.hugagent/source`,创建隔离 Python 环境、构建 Web 应用并进入
+首次配置向导,完成后打开 [http://127.0.0.1:3001](http://127.0.0.1:3001)。之后用
+`~/.hugagent/venv/bin/hugagent-server` 再次启动。
+
+> [!WARNING]
+> 默认仅监听 `127.0.0.1`。确需远程访问时用
+> `hugagent-server serve --host 0.0.0.0 --port 3001 --no-browser`,并先配置强密码、防火墙
+> 与 HTTPS,不要直接暴露在不受信任的网络中。
+
+一键安装使用 SQLite、进程内状态与本地子进程沙箱,适合试用与开发;选项与故障
+排查见[一键安装指南](./document/zh-CN/deployment/quick-install.md)。
## 系统架构
HugAgentOS 将用户渠道、智能体工作流、可复用能力引擎、本体契约、数据治理和
-基础设施分层组织。Action 契约把本体层与计划生成、审批校验和门控执行连接起来,
-安全管理与平台监管则贯穿完整技术栈。
+基础设施分层组织;Durable Run、租约与恢复机制贯穿执行链路,安全管理与平台监管
+贯穿完整技术栈。

> [!NOTE]
-> 架构图展示 HugAgentOS 的完整产品架构。其中部分治理、协作、网关和持久沙箱
-> 能力仅在商业版中提供。
+> 架构图展示 HugAgentOS 的完整产品架构。其中部分治理、协作和网关能力仅在商业版中提供。
### 技术栈
-项目选择成熟、可替换的开源组件,并通过清晰的服务边界组合为完整产品。
-
| 层级 | 主要技术 |
|---|---|
| 智能体运行时 | AgentScope 2.0、ReAct、Model Context Protocol |
| 后端 | Python、FastAPI、SQLAlchemy、Alembic |
| 前端 | React 19、TypeScript、Vite、Zustand、Ant Design |
-| 数据与状态 | SQLite 或 PostgreSQL 15、进程内状态或 Redis 7、本地文件存储 |
+| 桌面端 | Tauri 2、私有 CPython 3.11 运行时 |
+| 数据与状态 | SQLite 或 PostgreSQL 15、进程内状态或 Redis 7、本地或 S3 兼容存储 |
+| 沙箱 | OpenSandbox、本地子进程 |
| 可选记忆 | Milvus 2.4、Neo4j 5 Community、mem0 |
-| 部署 | 本地一键安装、Docker Compose、Nginx |
+| 部署 | 桌面安装包、一键安装、Docker Compose、Nginx |
更详细的请求生命周期、容器拓扑和关键设计决策见
[架构总览](./document/zh-CN/architecture/overview.md)。
## 社区版与商业版
-社区版让个人把智能体能力完整运行起来;商业版在相同产品体验上补充组织级治理、
-协作和交付能力。商业版能力不会以关闭开关的形式混入社区版源码。
+社区版让个人把完整的 Harness 与 Personal Agent 运行起来;商业版在相同产品体验上补充
+组织级治理、协作和交付能力。商业版能力不会以关闭开关的形式混入社区版源码。
| 社区版(CE) | 商业版(EE)新增 |
|---|---|
-| 智能对话、计划模式与个人子智能体 | 团队、成员、组织级智能体库与权限矩阵 |
-| 7 个通用 MCP、个人技能与技能市场 | 行业数据工具、组织级能力治理与技能审核 |
-| 私有知识库、三层个人记忆 | 公共知识库管理与记忆审计 |
+| Durable Run、自主循环、计划模式与个人子智能体 | 团队、成员、组织级智能体库与权限矩阵 |
+| Personal Agent:后台任务、专属电脑、渠道、调度与文件记忆 | 用户、角色、团队授权治理与跨设备会话管理 |
+| 通用 MCP、个人技能与技能市场 | 行业数据工具、组织级能力治理与技能审核 |
+| 私有知识库、三层个人记忆与个人进化 | 公共知识库管理与记忆审计 |
| 自动化、个人数据画布 | 组织计费、用量汇总与画布多人协同 |
-| OpenSandbox、快照与 S3 文件存储 | 团队挂载、其他云存储与离线商业交付 |
+| OpenSandbox、快照与 S3 文件存储;桌面本机模式 | 团队挂载、其他云存储与离线商业交付 |
| 本地账号与保留 Powered-by 的品牌配置 | SSO、审计合规与完整 white-label |
功能边界与升级路径以
@@ -219,24 +277,26 @@ HugAgentOS 将用户渠道、智能体工作流、可复用能力引擎、本体
## 文档
-仓库内提供完整的中英文文档,从首次部署到架构和二次开发均可离线阅读。
-
| 你想了解 | 中文文档 | English |
|---|---|---|
| 项目定位与整体能力 | [产品简介](./document/zh-CN/getting-started/introduction.md) | [Introduction](./document/en/getting-started/introduction.md) |
| 10 分钟运行项目 | [快速开始](./document/zh-CN/getting-started/quick-start.md) | [Quick start](./document/en/getting-started/quick-start.md) |
+| Run 生命周期与断线续播 | [智能对话](./document/zh-CN/modules/chat.md) | [Chat](./document/en/modules/chat.md) |
+| 7×24 个人智能体 | [Personal Agent](./document/zh-CN/modules/personal-agent.md) | [Personal Agent](./document/en/modules/personal-agent.md) |
+| 长时运行的自驱动任务 | [自主循环](./document/zh-CN/modules/autonomous-loop.md) | [Autonomous loop](./document/en/modules/autonomous-loop.md) |
+| 桌面本机与混合模式 | [桌面离线本机模式](./document/zh-CN/deployment/desktop-local-mode.md) | [Desktop local mode](./document/en/deployment/desktop-local-mode.md) |
+| 记忆与自进化 | [记忆系统](./document/zh-CN/modules/memory.md) | [Memory](./document/en/modules/memory.md) |
| 生产部署与环境变量 | [部署指南](./document/zh-CN/deployment/README.md) | [Deployment](./document/en/deployment/README.md) |
| 系统设计与请求链路 | [架构总览](./document/zh-CN/architecture/overview.md) | [Architecture](./document/en/architecture/overview.md) |
-| 快速构建领域本体 | [领域本体快速入门](./document/zh-CN/getting-started/domain-ontology-quickstart.md) | [Domain ontology quickstart](./document/en/getting-started/domain-ontology-quickstart.md) |
-| MCP、技能、记忆与沙箱 | [功能模块](./document/zh-CN/README.md#功能模块) | [Modules](./document/en/README.md#modules) |
+| 领域本体控制平面 | [领域本体快速入门](./document/zh-CN/getting-started/domain-ontology-quickstart.md) | [Domain ontology quickstart](./document/en/getting-started/domain-ontology-quickstart.md) |
| 后端与前端开发 | [开发指南](./document/zh-CN/README.md#开发指南) | [Development](./document/en/README.md#development) |
所有文档入口见 [document/README.md](./document/README.md)。
## 路线图
-1. **云端 / 本地无缝切换与多端互通** —— 一套服务端连接多客户端,同步会话、智能体、
- 技能、文件与任务状态。
+1. **Cloud / Local 无缝切换与多端互通** —— 在现有混合模式基础上,一套服务端连接多客户端,
+ 同步会话、智能体、技能、文件与任务状态。
2. **基于 MoA 的自适应模型路由** —— 按任务复杂度、模态、时延与成本选择或组合模型,
简单任务用轻量模型,复杂任务再升级。
3. **更丰富的扩展生态** —— 持续扩充智能体、技能、MCP 服务与插件,完善发现、安装、
diff --git a/desktop/requirements-desktop-linux-aarch64-py311.lock b/desktop/requirements-desktop-linux-aarch64-py311.lock
index 27cb3904..1420921a 100644
--- a/desktop/requirements-desktop-linux-aarch64-py311.lock
+++ b/desktop/requirements-desktop-linux-aarch64-py311.lock
@@ -1,8 +1,8 @@
# This file is generated. Do not edit it by hand.
# Regenerate with: npm --prefix desktop run lock:desktop
-# input-sha256: d18da0b813b601a5c727eb280b730ddb3b307fb487ab2a2bb28d4c568a336990
+# input-sha256: b9680ef14192ea099c4a7159c08c064cbd555bd681c00c49c5c74b1f7f3d4046
# Target: CPython 3.11 on Linux aarch64 (UOS 1070 / manylinux 2.28)
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
# via -r desktop/requirements-desktop.txt
aiofiles==25.1.0
# via agentscope
@@ -105,8 +105,6 @@ defusedxml==0.7.1
# via
# -r desktop/requirements-desktop.txt
# markitdown
-deprecated==1.3.1
- # via limits
dingtalk-stream==0.24.3
# via -r desktop/requirements-desktop.txt
distro==1.9.0
@@ -183,18 +181,18 @@ httpx==0.28.1
# huggingface-hub
# lark-oapi
# litellm
- # mcp
# mem0ai
# ollama
# openai
# qdrant-client
# tavily-python
httpx-sse==0.4.3
+ # via dashscope
+httpx2==2.12.0
# via
- # dashscope
+ # agentscope
+ # anthropic
# mcp
-httpx2==2.12.0
- # via anthropic
huggingface-hub==1.28.0
# via tokenizers
hyperframe==6.1.0
@@ -220,7 +218,7 @@ jmespath==1.1.0
# via
# boto3
# botocore
-json-repair==0.63.3
+json-repair==0.64.1
# via agentscope
json5==0.15.0
# via agentscope
@@ -228,6 +226,7 @@ jsonschema==4.26.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+ # json-repair
# litellm
# mcp
jsonschema-specifications==2025.9.1
@@ -236,8 +235,6 @@ kiwisolver==1.5.0
# via matplotlib
lark-oapi==1.7.3
# via -r desktop/requirements-desktop.txt
-limits==5.8.0
- # via slowapi
linkify-it-py==2.1.0
# via markdown-it-py
litellm==1.98.0
@@ -270,10 +267,12 @@ matplotlib==3.11.1
# via
# -r desktop/requirements-desktop.txt
# seaborn
-mcp==1.29.0
+mcp==2.3.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+mcp-types==2.3.0
+ # via mcp
mdit-py-plugins==0.6.1
# via textual
mdurl==0.1.2
@@ -316,6 +315,7 @@ openpyxl==3.1.5
opentelemetry-api==1.44.0
# via
# agentscope
+ # mcp
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-sdk
@@ -348,7 +348,6 @@ packaging==26.3
# via
# faiss-cpu
# huggingface-hub
- # limits
# matplotlib
# onnxruntime
# pikepdf
@@ -415,8 +414,10 @@ pydantic==2.13.4
# dashscope
# fastapi
# google-genai
+ # json-repair
# litellm
# mcp
+ # mcp-types
# mem0ai
# ollama
# openai
@@ -425,9 +426,7 @@ pydantic==2.13.4
pydantic-core==2.46.4
# via pydantic
pydantic-settings==2.15.0
- # via
- # litellm
- # mcp
+ # via litellm
pyee==13.0.1
# via playwright
pygments==2.21.0
@@ -441,7 +440,9 @@ pymilvus==2.5.18
pyparsing==3.3.2
# via matplotlib
pypdf==6.16.2
- # via -r desktop/requirements-desktop.txt
+ # via
+ # -r desktop/requirements-desktop.txt
+ # agentscope
pypdf2==3.0.1
# via -r desktop/requirements-desktop.txt
pypdfium2==5.13.0
@@ -518,11 +519,10 @@ requests-toolbelt==1.0.0
# via lark-oapi
rich==15.0.0
# via
+ # agentscope
# dashscope
# textual
# typer
-ripgrep==14.1.0
- # via agentscope
rpds-py==2026.6.3
# via
# jsonschema
@@ -549,8 +549,6 @@ six==1.17.0
# via
# markdownify
# python-dateutil
-slowapi==0.1.10
- # via -r desktop/requirements-desktop.txt
sniffio==1.3.1
# via
# anthropic
@@ -564,7 +562,9 @@ sqlalchemy==2.0.52
# alembic
# mem0ai
sse-starlette==3.0.3
- # via mcp
+ # via
+ # -r desktop/requirements-desktop.txt
+ # mcp
starlette==0.46.2
# via
# fastapi
@@ -615,8 +615,8 @@ typing-extensions==4.16.0
# grpcio
# httpx2
# huggingface-hub
- # limits
# mcp
+ # mcp-types
# openai
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-grpc
@@ -639,6 +639,8 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
+tzdata==2026.5
+ # via agentscope
uc-micro-py==2.0.0
# via linkify-it-py
ujson==5.13.0
@@ -667,8 +669,6 @@ werkzeug==3.1.8
# via -r desktop/requirements-desktop.txt
wheel==0.48.0
# via -r desktop/requirements-desktop.txt
-wrapt==2.3.0
- # via deprecated
wsproto==1.3.2
# via simple-websocket
xlsxwriter==3.2.9
diff --git a/desktop/requirements-desktop-linux-x86_64-py311.lock b/desktop/requirements-desktop-linux-x86_64-py311.lock
index 1ae48d54..a71239c2 100644
--- a/desktop/requirements-desktop-linux-x86_64-py311.lock
+++ b/desktop/requirements-desktop-linux-x86_64-py311.lock
@@ -1,8 +1,8 @@
# This file is generated. Do not edit it by hand.
# Regenerate with: npm --prefix desktop run lock:desktop
-# input-sha256: d18da0b813b601a5c727eb280b730ddb3b307fb487ab2a2bb28d4c568a336990
+# input-sha256: b9680ef14192ea099c4a7159c08c064cbd555bd681c00c49c5c74b1f7f3d4046
# Target: CPython 3.11 on Linux x86_64
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
# via -r desktop/requirements-desktop.txt
aiofiles==25.1.0
# via agentscope
@@ -31,6 +31,7 @@ anyio==4.14.2
# anthropic
# google-genai
# httpx
+ # httpx2
# mcp
# openai
# sse-starlette
@@ -98,8 +99,6 @@ defusedxml==0.7.1
# via
# -r desktop/requirements-desktop.txt
# markitdown
-deprecated==1.3.1
- # via limits
dingtalk-stream==0.24.3
# via -r desktop/requirements-desktop.txt
distro==1.9.0
@@ -155,6 +154,7 @@ grpcio==1.83.0
h11==0.16.0
# via
# httpcore
+ # httpcore2
# uvicorn
# wsproto
h2==4.4.0
@@ -165,6 +165,8 @@ hpack==4.2.0
# via h2
httpcore==1.0.9
# via httpx
+httpcore2==2.16.0
+ # via httpx2
httpx==0.28.1
# via
# -r desktop/requirements-desktop.txt
@@ -175,15 +177,16 @@ httpx==0.28.1
# huggingface-hub
# lark-oapi
# litellm
- # mcp
# mem0ai
# ollama
# openai
# qdrant-client
# tavily-python
httpx-sse==0.4.3
+ # via dashscope
+httpx2==2.16.0
# via
- # dashscope
+ # agentscope
# mcp
huggingface-hub==1.26.0
# via tokenizers
@@ -193,6 +196,7 @@ idna==3.18
# via
# anyio
# httpx
+ # httpx2
# requests
# yarl
importlib-metadata==8.9.0
@@ -205,7 +209,7 @@ jiter==0.16.0
# via
# anthropic
# openai
-json-repair==0.61.7
+json-repair==0.64.1
# via agentscope
json5==0.15.0
# via agentscope
@@ -213,6 +217,7 @@ jsonschema==4.26.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+ # json-repair
# litellm
# mcp
jsonschema-specifications==2025.9.1
@@ -221,8 +226,6 @@ kiwisolver==1.5.0
# via matplotlib
lark-oapi==1.7.1
# via -r desktop/requirements-desktop.txt
-limits==5.8.0
- # via slowapi
litellm==1.94.1
# via -r desktop/requirements-desktop.txt
lxml==6.1.1
@@ -250,10 +253,12 @@ matplotlib==3.11.1
# via
# -r desktop/requirements-desktop.txt
# seaborn
-mcp==1.29.0
+mcp==2.3.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+mcp-types==2.3.0
+ # via mcp
mdurl==0.1.2
# via markdown-it-py
mem0ai==2.0.14
@@ -294,6 +299,7 @@ openpyxl==3.1.5
opentelemetry-api==1.44.0
# via
# agentscope
+ # mcp
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-sdk
@@ -326,7 +332,6 @@ packaging==26.2
# via
# faiss-cpu
# huggingface-hub
- # limits
# matplotlib
# onnxruntime
# pikepdf
@@ -388,17 +393,16 @@ pydantic==2.13.4
# anthropic
# fastapi
# google-genai
+ # json-repair
# litellm
# mcp
+ # mcp-types
# mem0ai
# ollama
# openai
- # pydantic-settings
# qdrant-client
pydantic-core==2.46.4
# via pydantic
-pydantic-settings==2.14.2
- # via mcp
pyee==13.0.1
# via playwright
pygments==2.20.0
@@ -410,7 +414,9 @@ pymilvus==2.5.18
pyparsing==3.3.2
# via matplotlib
pypdf==6.14.2
- # via -r desktop/requirements-desktop.txt
+ # via
+ # -r desktop/requirements-desktop.txt
+ # agentscope
pypdf2==3.0.1
# via -r desktop/requirements-desktop.txt
pypdfium2==5.12.1
@@ -429,7 +435,6 @@ python-dotenv==1.2.2
# -r desktop/requirements-desktop.txt
# litellm
# magika
- # pydantic-settings
# pymilvus
python-engineio==4.13.3
# via python-socketio
@@ -485,10 +490,9 @@ requests-toolbelt==1.0.0
# via lark-oapi
rich==15.0.0
# via
+ # agentscope
# dashscope
# typer
-ripgrep==14.1.0
- # via agentscope
rpds-py==2026.6.3
# via
# jsonschema
@@ -513,8 +517,6 @@ six==1.17.0
# via
# markdownify
# python-dateutil
-slowapi==0.1.10
- # via -r desktop/requirements-desktop.txt
sniffio==1.3.1
# via
# anthropic
@@ -528,7 +530,9 @@ sqlalchemy==2.0.51
# alembic
# mem0ai
sse-starlette==3.0.3
- # via mcp
+ # via
+ # -r desktop/requirements-desktop.txt
+ # mcp
starlette==0.46.2
# via
# fastapi
@@ -556,6 +560,10 @@ tree-sitter==0.26.0
# via agentscope
tree-sitter-bash==0.25.1
# via agentscope
+truststore==0.10.5
+ # via
+ # httpcore2
+ # httpx2
typer==0.27.0
# via dashscope
typing-extensions==4.16.0
@@ -569,9 +577,10 @@ typing-extensions==4.16.0
# fastapi
# google-genai
# grpcio
+ # httpx2
# huggingface-hub
- # limits
# mcp
+ # mcp-types
# openai
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-grpc
@@ -592,7 +601,8 @@ typing-inspection==0.4.2
# via
# mcp
# pydantic
- # pydantic-settings
+tzdata==2026.5
+ # via agentscope
ujson==5.13.0
# via pymilvus
urllib3==2.7.0
@@ -616,8 +626,6 @@ werkzeug==3.1.8
# via -r desktop/requirements-desktop.txt
wheel==0.47.0
# via -r desktop/requirements-desktop.txt
-wrapt==2.3.0
- # via deprecated
wsproto==1.3.2
# via simple-websocket
xlsxwriter==3.2.9
diff --git a/desktop/requirements-desktop-macos-aarch64-py311.lock b/desktop/requirements-desktop-macos-aarch64-py311.lock
index 887a7ba2..9df1e05d 100644
--- a/desktop/requirements-desktop-macos-aarch64-py311.lock
+++ b/desktop/requirements-desktop-macos-aarch64-py311.lock
@@ -1,8 +1,8 @@
# This file is generated. Do not edit it by hand.
# Regenerate with: npm --prefix desktop run lock:desktop
-# input-sha256: 207ac56d3accd481bbd7d5fdd7de4ab076f3184f1dc6c7d0bd6de1d1b958d38a
+# input-sha256: 2580e67756923a30f416529f071ff7372044a681fb55cd05ef896e558846dd73
# Target: CPython 3.11 on macOS Apple Silicon
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
# via -r desktop/requirements-desktop.txt
aiofiles==25.1.0
# via agentscope
@@ -31,6 +31,7 @@ anyio==4.14.2
# anthropic
# google-genai
# httpx
+ # httpx2
# mcp
# openai
# sse-starlette
@@ -101,9 +102,7 @@ defusedxml==0.7.1
# -r desktop/requirements-desktop.txt
# markitdown
deprecated==1.3.1
- # via
- # limits
- # pikepdf
+ # via pikepdf
dingtalk-stream==0.24.3
# via -r desktop/requirements-desktop.txt
distro==1.9.0
@@ -157,6 +156,7 @@ grpcio==1.83.0
h11==0.16.0
# via
# httpcore
+ # httpcore2
# uvicorn
# wsproto
h2==4.4.0
@@ -167,6 +167,8 @@ hpack==4.2.0
# via h2
httpcore==1.0.9
# via httpx
+httpcore2==2.16.0
+ # via httpx2
httpx==0.28.1
# via
# -r desktop/requirements-desktop.txt
@@ -177,15 +179,16 @@ httpx==0.28.1
# huggingface-hub
# lark-oapi
# litellm
- # mcp
# mem0ai
# ollama
# openai
# qdrant-client
# tavily-python
httpx-sse==0.4.3
+ # via dashscope
+httpx2==2.16.0
# via
- # dashscope
+ # agentscope
# mcp
huggingface-hub==1.26.0
# via tokenizers
@@ -197,6 +200,7 @@ idna==3.18
# via
# anyio
# httpx
+ # httpx2
# requests
# yarl
importlib-metadata==8.9.0
@@ -209,7 +213,7 @@ jiter==0.16.0
# via
# anthropic
# openai
-json-repair==0.61.7
+json-repair==0.64.1
# via agentscope
json5==0.15.0
# via agentscope
@@ -217,6 +221,7 @@ jsonschema==4.26.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+ # json-repair
# litellm
# mcp
jsonschema-specifications==2025.9.1
@@ -225,8 +230,6 @@ kiwisolver==1.5.0
# via matplotlib
lark-oapi==1.7.1
# via -r desktop/requirements-desktop.txt
-limits==5.8.0
- # via slowapi
litellm==1.91.4
# via -r desktop/requirements-desktop.txt
lxml==6.1.1
@@ -254,10 +257,12 @@ matplotlib==3.11.1
# via
# -r desktop/requirements-desktop.txt
# seaborn
-mcp==1.29.0
+mcp==2.3.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+mcp-types==2.3.0
+ # via mcp
mdurl==0.1.2
# via markdown-it-py
mem0ai==2.0.14
@@ -300,6 +305,7 @@ openpyxl==3.1.5
opentelemetry-api==1.44.0
# via
# agentscope
+ # mcp
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-sdk
@@ -332,7 +338,6 @@ packaging==26.2
# via
# faiss-cpu
# huggingface-hub
- # limits
# matplotlib
# onnxruntime
# pikepdf
@@ -396,17 +401,16 @@ pydantic==2.13.4
# anthropic
# fastapi
# google-genai
+ # json-repair
# litellm
# mcp
+ # mcp-types
# mem0ai
# ollama
# openai
- # pydantic-settings
# qdrant-client
pydantic-core==2.46.4
# via pydantic
-pydantic-settings==2.14.2
- # via mcp
pyee==13.0.1
# via playwright
pygments==2.20.0
@@ -418,7 +422,9 @@ pymilvus==2.5.18
pyparsing==3.3.2
# via matplotlib
pypdf==6.14.2
- # via -r desktop/requirements-desktop.txt
+ # via
+ # -r desktop/requirements-desktop.txt
+ # agentscope
pypdf2==3.0.1
# via -r desktop/requirements-desktop.txt
pypdfium2==5.12.1
@@ -437,7 +443,6 @@ python-dotenv==1.2.2
# -r desktop/requirements-desktop.txt
# litellm
# magika
- # pydantic-settings
# pymilvus
python-engineio==4.13.3
# via python-socketio
@@ -493,10 +498,9 @@ requests-toolbelt==1.0.0
# via lark-oapi
rich==15.0.0
# via
+ # agentscope
# dashscope
# typer
-ripgrep==15.1.0
- # via agentscope
rpds-py==2026.6.3
# via
# jsonschema
@@ -521,8 +525,6 @@ six==1.17.0
# via
# markdownify
# python-dateutil
-slowapi==0.1.10
- # via -r desktop/requirements-desktop.txt
sniffio==1.3.1
# via
# anthropic
@@ -536,7 +538,9 @@ sqlalchemy==2.0.51
# alembic
# mem0ai
sse-starlette==3.0.3
- # via mcp
+ # via
+ # -r desktop/requirements-desktop.txt
+ # mcp
starlette==0.46.2
# via
# fastapi
@@ -566,6 +570,10 @@ tree-sitter==0.26.0
# via agentscope
tree-sitter-bash==0.25.1
# via agentscope
+truststore==0.10.5
+ # via
+ # httpcore2
+ # httpx2
typer==0.27.0
# via dashscope
typing-extensions==4.16.0
@@ -579,9 +587,10 @@ typing-extensions==4.16.0
# fastapi
# google-genai
# grpcio
+ # httpx2
# huggingface-hub
- # limits
# mcp
+ # mcp-types
# openai
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-grpc
@@ -602,7 +611,8 @@ typing-inspection==0.4.2
# via
# mcp
# pydantic
- # pydantic-settings
+tzdata==2026.5
+ # via agentscope
ujson==5.13.0
# via pymilvus
urllib3==2.7.0
diff --git a/desktop/requirements-desktop-macos-x86_64-py311.lock b/desktop/requirements-desktop-macos-x86_64-py311.lock
index 4118f46d..39a17b34 100644
--- a/desktop/requirements-desktop-macos-x86_64-py311.lock
+++ b/desktop/requirements-desktop-macos-x86_64-py311.lock
@@ -1,8 +1,8 @@
# This file is generated. Do not edit it by hand.
# Regenerate with: npm --prefix desktop run lock:desktop
-# input-sha256: 207ac56d3accd481bbd7d5fdd7de4ab076f3184f1dc6c7d0bd6de1d1b958d38a
+# input-sha256: 2580e67756923a30f416529f071ff7372044a681fb55cd05ef896e558846dd73
# Target: CPython 3.11 on macOS Intel
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
# via -r desktop/requirements-desktop.txt
aiofiles==25.1.0
# via agentscope
@@ -31,6 +31,7 @@ anyio==4.14.2
# anthropic
# google-genai
# httpx
+ # httpx2
# mcp
# openai
# sse-starlette
@@ -101,9 +102,7 @@ defusedxml==0.7.1
# -r desktop/requirements-desktop.txt
# markitdown
deprecated==1.3.1
- # via
- # limits
- # pikepdf
+ # via pikepdf
dingtalk-stream==0.24.3
# via -r desktop/requirements-desktop.txt
distro==1.9.0
@@ -159,6 +158,7 @@ grpcio==1.83.0
h11==0.16.0
# via
# httpcore
+ # httpcore2
# uvicorn
# wsproto
h2==4.4.0
@@ -169,6 +169,8 @@ hpack==4.2.0
# via h2
httpcore==1.0.9
# via httpx
+httpcore2==2.16.0
+ # via httpx2
httpx==0.28.1
# via
# -r desktop/requirements-desktop.txt
@@ -179,15 +181,16 @@ httpx==0.28.1
# huggingface-hub
# lark-oapi
# litellm
- # mcp
# mem0ai
# ollama
# openai
# qdrant-client
# tavily-python
httpx-sse==0.4.3
+ # via dashscope
+httpx2==2.16.0
# via
- # dashscope
+ # agentscope
# mcp
huggingface-hub==1.26.0
# via tokenizers
@@ -199,6 +202,7 @@ idna==3.18
# via
# anyio
# httpx
+ # httpx2
# requests
# yarl
importlib-metadata==8.9.0
@@ -211,7 +215,7 @@ jiter==0.16.0
# via
# anthropic
# openai
-json-repair==0.61.7
+json-repair==0.64.1
# via agentscope
json5==0.15.0
# via agentscope
@@ -219,6 +223,7 @@ jsonschema==4.26.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+ # json-repair
# litellm
# mcp
jsonschema-specifications==2025.9.1
@@ -227,8 +232,6 @@ kiwisolver==1.5.0
# via matplotlib
lark-oapi==1.7.1
# via -r desktop/requirements-desktop.txt
-limits==5.8.0
- # via slowapi
litellm==1.91.4
# via -r desktop/requirements-desktop.txt
lxml==6.1.1
@@ -256,10 +259,12 @@ matplotlib==3.11.1
# via
# -r desktop/requirements-desktop.txt
# seaborn
-mcp==1.29.0
+mcp==2.3.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+mcp-types==2.3.0
+ # via mcp
mdurl==0.1.2
# via markdown-it-py
mem0ai==2.0.14
@@ -302,6 +307,7 @@ openpyxl==3.1.5
opentelemetry-api==1.44.0
# via
# agentscope
+ # mcp
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-sdk
@@ -334,7 +340,6 @@ packaging==26.2
# via
# faiss-cpu
# huggingface-hub
- # limits
# matplotlib
# onnxruntime
# pikepdf
@@ -398,17 +403,16 @@ pydantic==2.13.4
# anthropic
# fastapi
# google-genai
+ # json-repair
# litellm
# mcp
+ # mcp-types
# mem0ai
# ollama
# openai
- # pydantic-settings
# qdrant-client
pydantic-core==2.46.4
# via pydantic
-pydantic-settings==2.14.2
- # via mcp
pyee==13.0.1
# via playwright
pygments==2.20.0
@@ -420,7 +424,9 @@ pymilvus==2.5.18
pyparsing==3.3.2
# via matplotlib
pypdf==6.14.2
- # via -r desktop/requirements-desktop.txt
+ # via
+ # -r desktop/requirements-desktop.txt
+ # agentscope
pypdf2==3.0.1
# via -r desktop/requirements-desktop.txt
pypdfium2==5.12.1
@@ -439,7 +445,6 @@ python-dotenv==1.2.2
# -r desktop/requirements-desktop.txt
# litellm
# magika
- # pydantic-settings
# pymilvus
python-engineio==4.13.3
# via python-socketio
@@ -495,10 +500,9 @@ requests-toolbelt==1.0.0
# via lark-oapi
rich==15.0.0
# via
+ # agentscope
# dashscope
# typer
-ripgrep==14.1.0
- # via agentscope
rpds-py==2026.6.3
# via
# jsonschema
@@ -523,8 +527,6 @@ six==1.17.0
# via
# markdownify
# python-dateutil
-slowapi==0.1.10
- # via -r desktop/requirements-desktop.txt
sniffio==1.3.1
# via
# anthropic
@@ -538,7 +540,9 @@ sqlalchemy==2.0.51
# alembic
# mem0ai
sse-starlette==3.0.3
- # via mcp
+ # via
+ # -r desktop/requirements-desktop.txt
+ # mcp
starlette==0.46.2
# via
# fastapi
@@ -568,6 +572,10 @@ tree-sitter==0.26.0
# via agentscope
tree-sitter-bash==0.25.1
# via agentscope
+truststore==0.10.5
+ # via
+ # httpcore2
+ # httpx2
typer==0.27.0
# via dashscope
typing-extensions==4.16.0
@@ -581,9 +589,10 @@ typing-extensions==4.16.0
# fastapi
# google-genai
# grpcio
+ # httpx2
# huggingface-hub
- # limits
# mcp
+ # mcp-types
# openai
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-grpc
@@ -604,7 +613,8 @@ typing-inspection==0.4.2
# via
# mcp
# pydantic
- # pydantic-settings
+tzdata==2026.5
+ # via agentscope
ujson==5.13.0
# via pymilvus
urllib3==2.7.0
diff --git a/desktop/requirements-desktop-windows-py311.lock b/desktop/requirements-desktop-windows-py311.lock
index 36895fe1..48953834 100644
--- a/desktop/requirements-desktop-windows-py311.lock
+++ b/desktop/requirements-desktop-windows-py311.lock
@@ -1,8 +1,8 @@
# This file is generated. Do not edit it by hand.
# Regenerate with: npm --prefix desktop run lock:windows
-# input-sha256: d18da0b813b601a5c727eb280b730ddb3b307fb487ab2a2bb28d4c568a336990
+# input-sha256: b9680ef14192ea099c4a7159c08c064cbd555bd681c00c49c5c74b1f7f3d4046
# Target: CPython 3.11 on Windows x86_64
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
# via -r desktop/requirements-desktop.txt
aiofiles==25.1.0
# via agentscope
@@ -31,6 +31,7 @@ anyio==4.14.2
# anthropic
# google-genai
# httpx
+ # httpx2
# mcp
# openai
# sse-starlette
@@ -106,8 +107,6 @@ defusedxml==0.7.1
# via
# -r desktop/requirements-desktop.txt
# markitdown
-deprecated==1.3.1
- # via limits
dingtalk-stream==0.24.3
# via -r desktop/requirements-desktop.txt
distro==1.9.0
@@ -163,6 +162,7 @@ grpcio==1.83.0
h11==0.16.0
# via
# httpcore
+ # httpcore2
# uvicorn
# wsproto
h2==4.4.0
@@ -173,6 +173,8 @@ hpack==4.2.0
# via h2
httpcore==1.0.9
# via httpx
+httpcore2==2.16.0
+ # via httpx2
httpx==0.28.1
# via
# -r desktop/requirements-desktop.txt
@@ -183,15 +185,16 @@ httpx==0.28.1
# huggingface-hub
# lark-oapi
# litellm
- # mcp
# mem0ai
# ollama
# openai
# qdrant-client
# tavily-python
httpx-sse==0.4.3
+ # via dashscope
+httpx2==2.16.0
# via
- # dashscope
+ # agentscope
# mcp
huggingface-hub==1.26.0
# via tokenizers
@@ -203,6 +206,7 @@ idna==3.18
# via
# anyio
# httpx
+ # httpx2
# requests
# yarl
importlib-metadata==8.9.0
@@ -215,7 +219,7 @@ jiter==0.16.0
# via
# anthropic
# openai
-json-repair==0.61.7
+json-repair==0.64.1
# via agentscope
json5==0.15.0
# via agentscope
@@ -223,6 +227,7 @@ jsonschema==4.26.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+ # json-repair
# litellm
# mcp
jsonschema-specifications==2025.9.1
@@ -231,8 +236,6 @@ kiwisolver==1.5.0
# via matplotlib
lark-oapi==1.7.1
# via -r desktop/requirements-desktop.txt
-limits==5.8.0
- # via slowapi
litellm==1.94.1
# via -r desktop/requirements-desktop.txt
lxml==6.1.1
@@ -260,10 +263,12 @@ matplotlib==3.11.1
# via
# -r desktop/requirements-desktop.txt
# seaborn
-mcp==1.29.0
+mcp==2.3.0
# via
# -r desktop/requirements-desktop.txt
# agentscope
+mcp-types==2.3.0
+ # via mcp
mdurl==0.1.2
# via markdown-it-py
mem0ai==2.0.14
@@ -304,6 +309,7 @@ openpyxl==3.1.5
opentelemetry-api==1.44.0
# via
# agentscope
+ # mcp
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-sdk
@@ -336,7 +342,6 @@ packaging==26.2
# via
# faiss-cpu
# huggingface-hub
- # limits
# matplotlib
# onnxruntime
# pikepdf
@@ -398,17 +403,16 @@ pydantic==2.13.4
# anthropic
# fastapi
# google-genai
+ # json-repair
# litellm
# mcp
+ # mcp-types
# mem0ai
# ollama
# openai
- # pydantic-settings
# qdrant-client
pydantic-core==2.46.4
# via pydantic
-pydantic-settings==2.14.2
- # via mcp
pyee==13.0.1
# via playwright
pygments==2.20.0
@@ -420,7 +424,9 @@ pymilvus==2.5.18
pyparsing==3.3.2
# via matplotlib
pypdf==6.14.2
- # via -r desktop/requirements-desktop.txt
+ # via
+ # -r desktop/requirements-desktop.txt
+ # agentscope
pypdf2==3.0.1
# via -r desktop/requirements-desktop.txt
pypdfium2==5.12.1
@@ -441,7 +447,6 @@ python-dotenv==1.2.2
# -r desktop/requirements-desktop.txt
# litellm
# magika
- # pydantic-settings
# pymilvus
python-engineio==4.13.3
# via python-socketio
@@ -501,10 +506,9 @@ requests-toolbelt==1.0.0
# via lark-oapi
rich==15.0.0
# via
+ # agentscope
# dashscope
# typer
-ripgrep==14.1.0
- # via agentscope
rpds-py==2026.6.3
# via
# jsonschema
@@ -529,8 +533,6 @@ six==1.17.0
# via
# markdownify
# python-dateutil
-slowapi==0.1.10
- # via -r desktop/requirements-desktop.txt
sniffio==1.3.1
# via
# anthropic
@@ -544,7 +546,9 @@ sqlalchemy==2.0.51
# alembic
# mem0ai
sse-starlette==3.0.3
- # via mcp
+ # via
+ # -r desktop/requirements-desktop.txt
+ # mcp
starlette==0.46.2
# via
# fastapi
@@ -574,6 +578,10 @@ tree-sitter==0.26.0
# via agentscope
tree-sitter-bash==0.25.1
# via agentscope
+truststore==0.10.5
+ # via
+ # httpcore2
+ # httpx2
typer==0.27.0
# via dashscope
typing-extensions==4.16.0
@@ -587,9 +595,10 @@ typing-extensions==4.16.0
# fastapi
# google-genai
# grpcio
+ # httpx2
# huggingface-hub
- # limits
# mcp
+ # mcp-types
# openai
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-grpc
@@ -610,9 +619,10 @@ typing-inspection==0.4.2
# via
# mcp
# pydantic
- # pydantic-settings
tzdata==2026.3
- # via pandas
+ # via
+ # agentscope
+ # pandas
ujson==5.13.0
# via pymilvus
urllib3==2.7.0
@@ -636,8 +646,6 @@ werkzeug==3.1.8
# via -r desktop/requirements-desktop.txt
wheel==0.47.0
# via -r desktop/requirements-desktop.txt
-wrapt==2.3.0
- # via deprecated
wsproto==1.3.2
# via simple-websocket
xlsxwriter==3.2.9
diff --git a/desktop/requirements-desktop.txt b/desktop/requirements-desktop.txt
index e2937d7a..9b2d2192 100644
--- a/desktop/requirements-desktop.txt
+++ b/desktop/requirements-desktop.txt
@@ -18,17 +18,19 @@ uv==0.11.33
# Core web service and orchestration.
fastapi==0.115.12
uvicorn>=0.27.0
-pydantic>=2.6.0
-anyio>=4.2.0
+pydantic>=2.12.0
+anyio>=4.9.0
python-dotenv>=1.0.0
requests>=2.31.0
httpx>=0.27.0
jsonschema>=4.20.0
-agentscope==2.0.0
+agentscope @ https://github.com/agentscope-ai/agentscope/archive/ed357feb78a45ff02df155bb5610eab9bf543832.tar.gz
litellm>=1.50.0
google-genai>=0.3.0
ollama>=0.3.0
-mcp>=1.0.0,<2.0.0
+mcp==2.3.0
+# Keep the SSE dependency within FastAPI 0.115 Starlette constraints.
+sse-starlette==3.0.3
tavily-python>=0.5.0
beautifulsoup4>=4.12.0
@@ -40,7 +42,6 @@ redis>=5.0.0
werkzeug>=3.0.0
python-multipart>=0.0.9
structlog>=23.2.0
-slowapi>=0.1.9
argon2-cffi>=23.1.0
croniter>=2.0.0
pytz>=2024.1
diff --git a/desktop/scripts/build-runtime.mjs b/desktop/scripts/build-runtime.mjs
index bb4a1430..dc159fc4 100644
--- a/desktop/scripts/build-runtime.mjs
+++ b/desktop/scripts/build-runtime.mjs
@@ -112,6 +112,9 @@ export function buildDesktopRuntime({ desktopDir, repoRoot, sourceRoot, python }
"--break-system-packages",
"--only-binary",
":all:",
+ // AgentScope is pinned to a reviewed source commit; other packages require wheels.
+ "--no-binary",
+ "agentscope",
join(repoRoot, config.lockFile),
],
{ cwd: repoRoot },
diff --git a/desktop/scripts/desktop-dependencies.test.mjs b/desktop/scripts/desktop-dependencies.test.mjs
index 2f28f373..c6540fbc 100644
--- a/desktop/scripts/desktop-dependencies.test.mjs
+++ b/desktop/scripts/desktop-dependencies.test.mjs
@@ -18,11 +18,26 @@ import {
WINDOWS_DESKTOP_LOCK_FILE,
desktopDependencyFingerprint,
readAndValidateWindowsDesktopLock,
+ readAndValidateDesktopLock,
} from "./desktop-dependencies.mjs";
const desktopDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const repoRoot = resolve(desktopDir, "..");
+function assertExactLock(lock) {
+ const source = readFileSync(join(repoRoot, DESKTOP_REQUIREMENTS_FILE), "utf8")
+ .match(/^agentscope @ https:\/\/github\.com\/agentscope-ai\/agentscope\/archive\/[a-f0-9]{40}\.tar\.gz$/m)?.[0];
+ assert.ok(source, "AgentScope must use an immutable commit archive");
+ assert.ok(readFileSync(join(repoRoot, "requirements.txt"), "utf8").split(/\r?\n/).includes(source));
+ assert.ok(lock.split(/\r?\n/).includes(source));
+ for (const line of lock.split(/\r?\n/)) {
+ if (/^[A-Za-z0-9_.-]+/.test(line) && line !== source) {
+ assert.match(line, /^[A-Za-z0-9_.-]+==[^\s]+$/);
+ }
+ }
+ assert.match(lock, /^mcp==2\.3\.0$/m);
+}
+
function requirementNames(contents) {
return new Set(
contents
@@ -73,14 +88,7 @@ test("Windows Python 3.11 lock is exact and matches its desktop input", () => {
const lock = readAndValidateWindowsDesktopLock(repoRoot);
const names = requirementNames(lock);
assert.ok(names.size > 100, "the transitive desktop lock must be complete");
- for (const line of lock.split(/\r?\n/)) {
- if (/^[A-Za-z0-9_.-]+/.test(line)) {
- assert.match(line, /^[A-Za-z0-9_.-]+==[^\s]+$/);
- }
- }
- const mcpVersion = lock.match(/^mcp==([^\s]+)$/m)?.[1];
- assert.ok(mcpVersion, "the lock must contain mcp");
- assert.equal(Number(mcpVersion.split(".")[0]), 1);
+ assertExactLock(lock);
assert.match(lock, /^pymilvus==2\.5\.18$/m);
assert.match(lock, /^milvus-lite==3\.2\.2$/m);
// Some platform locks pull boto3 through LiteLLM; it is not a storage backend.
@@ -124,14 +132,9 @@ test("desktop dependency hash is independent of checkout line endings", () => {
test("all supported desktop targets have exact Python 3.11 locks", () => {
for (const [target, config] of Object.entries(DESKTOP_TARGETS)) {
- const lock = readFileSync(join(repoRoot, config.lockFile), "utf8");
- assert.match(lock, /# input-sha256: [a-f0-9]{64}/);
+ const lock = readAndValidateDesktopLock(repoRoot, target);
assert.ok(requirementNames(lock).size > 100, `${target} lock is incomplete`);
- for (const line of lock.split(/\r?\n/)) {
- if (/^[A-Za-z0-9_.-]+/.test(line)) {
- assert.match(line, /^[A-Za-z0-9_.-]+==[^\s]+$/);
- }
- }
+ assertExactLock(lock);
}
});
diff --git a/desktop/scripts/test-native-tools.py b/desktop/scripts/test-native-tools.py
index fa8e40ee..9e076108 100644
--- a/desktop/scripts/test-native-tools.py
+++ b/desktop/scripts/test-native-tools.py
@@ -174,7 +174,7 @@ def test_backend_prefers_relocated_private_tools_without_host_path(self):
import importlib.util
from unittest.mock import patch
spec = importlib.util.spec_from_file_location(
- "office_helpers", SCRIPT.parents[2] / "src/backend/core/content/office.py")
+ "office_helpers", SCRIPT.parents[2] / "src/backend/core/files/content/office.py")
office = importlib.util.module_from_spec(spec)
spec.loader.exec_module(office)
with tempfile.TemporaryDirectory() as folder:
diff --git a/docker-compose.yml b/docker-compose.yml
index c8d35b9b..37900643 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -73,8 +73,7 @@ services:
BRAND_POWERED_BY: ${BRAND_POWERED_BY:-true}
DATABASE_URL: postgresql://hugagent_user:${DB_PASSWORD:-hugagent_dev_password}@postgres:5432/hugagent
APPLICATION_DATABASE_URL: ${APPLICATION_DATABASE_URL:-}
- DB_POOL_SIZE: ${DB_POOL_SIZE:-100}
- DB_POOL_MAX_OVERFLOW: ${DB_POOL_MAX_OVERFLOW:-20}
+ WORKER_THREADS: ${WORKER_THREADS:-40}
DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-30}
WEB_CONCURRENCY: ${WEB_CONCURRENCY:-2}
ENV: ${ENV:-dev}
@@ -128,8 +127,6 @@ services:
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-}
S3_SECRET_KEY: ${S3_SECRET_KEY:-}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000,http://localhost:5173}
- RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true}
- RATE_LIMIT_DEFAULT: ${RATE_LIMIT_DEFAULT:-100/minute}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
LOG_FORMAT: ${LOG_FORMAT:-json}
LOG_TO_FILE: ${LOG_TO_FILE:-false}
@@ -154,6 +151,7 @@ services:
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
BAIDU_API_KEY: ${BAIDU_API_KEY:-}
LANGSEARCH_API_KEY: ${LANGSEARCH_API_KEY:-}
+ DEEPSEEK_SEARCH_API_KEY: ${DEEPSEEK_SEARCH_API_KEY:-}
MEM0_ENABLED: ${MEM0_ENABLED:-false}
MEM0_GRAPH_ENABLED: ${MEM0_GRAPH_ENABLED:-false}
MEMORY_MODEL_URL: ${MEMORY_MODEL_URL:-}
@@ -270,8 +268,7 @@ services:
environment:
TZ: ${TZ:-Asia/Shanghai}
DATABASE_URL: postgresql://hugagent_user:${DB_PASSWORD:-hugagent_dev_password}@postgres:5432/hugagent
- DB_POOL_SIZE: ${DB_POOL_SIZE:-100}
- DB_POOL_MAX_OVERFLOW: ${DB_POOL_MAX_OVERFLOW:-20}
+ WORKER_THREADS: ${WORKER_THREADS:-40}
DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-30}
STORAGE_TYPE: ${STORAGE_TYPE:-s3}
STORAGE_PATH: /app/storage
@@ -295,6 +292,7 @@ services:
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
BAIDU_API_KEY: ${BAIDU_API_KEY:-}
LANGSEARCH_API_KEY: ${LANGSEARCH_API_KEY:-}
+ DEEPSEEK_SEARCH_API_KEY: ${DEEPSEEK_SEARCH_API_KEY:-}
QUERY_DATABASE_URL: ${QUERY_DATABASE_URL:-}
QUERY_DATABASE_TIMEOUT_SECONDS: ${QUERY_DATABASE_TIMEOUT_SECONDS:-}
QUERY_DATABASE_RETRY_TIMES: ${QUERY_DATABASE_RETRY_TIMES:-}
@@ -369,7 +367,7 @@ services:
- script_runner
build:
context: .
- dockerfile: docker/Dockerfile.script-runner
+ dockerfile: docker/sandbox/Dockerfile.script-runner
container_name: hugagent-script-runner
logging: *id001
environment:
@@ -414,8 +412,8 @@ services:
COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:?COMPOSE_PROJECT_NAME must be explicit}
OPENSANDBOX_API_KEY: ${OPENSANDBOX_API_KEY:-}
volumes:
- - ./docker/opensandbox-config.toml.tpl:/tpl/config.toml.tpl:ro
- - ./docker/render-opensandbox-config.py:/tpl/render.py:ro
+ - ./docker/opensandbox-server/opensandbox-config.toml.tpl:/tpl/config.toml.tpl:ro
+ - ./docker/opensandbox-server/render-opensandbox-config.py:/tpl/render.py:ro
- opensandbox_config:/out
command:
- python
@@ -429,7 +427,7 @@ services:
image: hugagent-opensandbox-server:v0.1.13-3
build:
context: .
- dockerfile: docker/Dockerfile.opensandbox-server
+ dockerfile: docker/opensandbox-server/Dockerfile.opensandbox-server
container_name: hugagent-opensandbox
logging: *id001
depends_on:
@@ -438,6 +436,7 @@ services:
environment:
TZ: ${TZ:-Asia/Shanghai}
OPENSANDBOX_INSECURE_SERVER: 'NO'
+ OPENSANDBOX_POOL_MAX_TOTAL: ${OPENSANDBOX_POOL_MAX_TOTAL:-20}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- opensandbox_config:/etc/opensandbox:ro
diff --git a/docker/Dockerfile.opensandbox-server.dockerignore b/docker/Dockerfile.opensandbox-server.dockerignore
deleted file mode 100644
index 2b3b7e78..00000000
--- a/docker/Dockerfile.opensandbox-server.dockerignore
+++ /dev/null
@@ -1,16 +0,0 @@
-**
-!docker/
-!docker/patch_opensandbox_server.py
-!docker/opensandbox_expirations.py
-!docker/opensandbox_mount_worker.py
-!docker/opensandbox_mount_recovery.py
-!docker/opensandbox_object_mounts.py
-!src/
-!src/backend/
-!src/backend/core/
-!src/backend/core/sandbox/
-!src/backend/core/sandbox/sessions/
-!src/backend/core/sandbox/sessions/metadata.py
-!src/backend/tests/
-!src/backend/tests/sandbox/
-!src/backend/tests/sandbox/test_opensandbox_controller_patch.py
diff --git a/docker/Dockerfile.personal-agent-test b/docker/Dockerfile.personal-agent-test
deleted file mode 100644
index d689f1ab..00000000
--- a/docker/Dockerfile.personal-agent-test
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM hugagent-backend:latest
-USER root
-COPY --chown=appuser:appuser src/backend /app/src/backend
-COPY --chown=appuser:appuser alembic.ini /app/alembic.ini
-USER appuser
-CMD ["bash", "/app/src/backend/scripts/backend_entrypoint.sh"]
diff --git a/docker-compose.application-hosting.yml b/docker/application-hosting/compose.application-hosting.yml
similarity index 90%
rename from docker-compose.application-hosting.yml
rename to docker/application-hosting/compose.application-hosting.yml
index 74c1e7c7..3f8c5808 100644
--- a/docker-compose.application-hosting.yml
+++ b/docker/application-hosting/compose.application-hosting.yml
@@ -1,5 +1,5 @@
# Opt-in application data database, separate from the platform postgres service.
-# docker compose -f docker-compose.yml -f docker-compose.application-hosting.yml up -d
+# docker compose -f docker-compose.yml -f docker/application-hosting/compose.application-hosting.yml up -d
services:
application-postgres:
image: postgres:15-alpine
@@ -29,7 +29,7 @@ services:
target: production
volumes:
- ./src/backend:/app/src/backend:ro
- command: ["python", "-m", "core.services.application_hosting_setup"]
+ command: ["python", "-m", "features.hosted_apps.hosting_setup"]
environment:
APPLICATION_DATABASE_URL: postgresql://application_owner:${APPLICATION_OWNER_DB_PASSWORD:?Set a separate APPLICATION_OWNER_DB_PASSWORD}@application-postgres:5432/applications
APPLICATION_PROVISION_DATABASE_URL: postgresql://application_admin:${APPLICATION_DB_PASSWORD}@application-postgres:5432/applications
diff --git a/docker/Dockerfile.object-storage b/docker/object-storage/Dockerfile.object-storage
similarity index 100%
rename from docker/Dockerfile.object-storage
rename to docker/object-storage/Dockerfile.object-storage
diff --git a/docker/compose.object-storage.yml b/docker/object-storage/compose.object-storage.yml
similarity index 80%
rename from docker/compose.object-storage.yml
rename to docker/object-storage/compose.object-storage.yml
index b0c46c20..21d79236 100644
--- a/docker/compose.object-storage.yml
+++ b/docker/object-storage/compose.object-storage.yml
@@ -4,14 +4,15 @@ services:
image: ${COMPOSE_PROJECT_NAME:-hugagent}-object-storage:RELEASE.2023-03-13T19-46-17Z
build:
context: ..
- dockerfile: docker/Dockerfile.object-storage
+ dockerfile: docker/object-storage/Dockerfile.object-storage
command: ["server", "/data", "--console-address", ":9001"]
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY:?Set a random S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:?Set a random S3_SECRET_KEY}
volumes:
- object_storage_data:/data
- networks: [hugagent-network]
+ # Personal Agent object mounts run s3fs in a helper on the compute network.
+ networks: [hugagent-network, personal-agent-compute]
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:9000/minio/health/live', timeout=5).read()"]
interval: 10s
@@ -31,7 +32,7 @@ services:
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?Set S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
volumes:
- - ./docker/init-object-storage.py:/init-object-storage.py:ro
+ - ./docker/object-storage/init-object-storage.py:/init-object-storage.py:ro
depends_on:
object-storage: {condition: service_healthy}
networks: [hugagent-network]
@@ -42,5 +43,8 @@ services:
mcp:
depends_on:
object-storage-init: {condition: service_completed_successfully}
+networks:
+ personal-agent-compute:
+ name: ${COMPOSE_PROJECT_NAME:-hugagent}_personal-agent-compute
volumes:
object_storage_data:
diff --git a/docker/init-object-storage.py b/docker/object-storage/init-object-storage.py
similarity index 100%
rename from docker/init-object-storage.py
rename to docker/object-storage/init-object-storage.py
diff --git a/docker/Dockerfile.opensandbox-server b/docker/opensandbox-server/Dockerfile.opensandbox-server
similarity index 52%
rename from docker/Dockerfile.opensandbox-server
rename to docker/opensandbox-server/Dockerfile.opensandbox-server
index 95b92d71..9ff52a54 100644
--- a/docker/Dockerfile.opensandbox-server
+++ b/docker/opensandbox-server/Dockerfile.opensandbox-server
@@ -2,12 +2,13 @@
FROM opensandbox/server:v0.1.13
USER root
RUN apt-get update && apt-get install -y --no-install-recommends s3fs fuse && rm -rf /var/lib/apt/lists/*
-COPY docker/opensandbox_expirations.py /app/opensandbox_server/services/docker/expirations.py
-COPY docker/opensandbox_mount_worker.py /app/opensandbox_server/services/docker/mount_worker.py
+COPY docker/opensandbox-server/opensandbox_capacity.py /app/opensandbox_server/services/docker/capacity.py
+COPY docker/opensandbox-server/opensandbox_expirations.py /app/opensandbox_server/services/docker/expirations.py
+COPY docker/opensandbox-server/opensandbox_mount_worker.py /app/opensandbox_server/services/docker/mount_worker.py
COPY src/backend/core/sandbox/sessions/metadata.py /app/opensandbox_server/services/docker/sandbox_metadata.py
-COPY docker/opensandbox_mount_recovery.py /app/opensandbox_server/services/docker/mount_recovery.py
-COPY docker/opensandbox_object_mounts.py /app/opensandbox_server/services/docker/object_mounts.py
-COPY docker/patch_opensandbox_server.py /tmp/hugagent-controller/patch.py
+COPY docker/opensandbox-server/opensandbox_mount_recovery.py /app/opensandbox_server/services/docker/mount_recovery.py
+COPY docker/opensandbox-server/opensandbox_object_mounts.py /app/opensandbox_server/services/docker/object_mounts.py
+COPY docker/opensandbox-server/patch_opensandbox_server.py /tmp/hugagent-controller/patch.py
COPY src/backend/tests/sandbox/test_opensandbox_controller_patch.py /tmp/hugagent-controller/test_patch.py
RUN OPENSANDBOX_PATCH_SCRIPT=/tmp/hugagent-controller/patch.py \
OPENSANDBOX_TEST_SOURCE=/app/opensandbox_server \
diff --git a/docker/opensandbox-server/Dockerfile.opensandbox-server.dockerignore b/docker/opensandbox-server/Dockerfile.opensandbox-server.dockerignore
new file mode 100644
index 00000000..9f99abbe
--- /dev/null
+++ b/docker/opensandbox-server/Dockerfile.opensandbox-server.dockerignore
@@ -0,0 +1,18 @@
+**
+!docker/
+!docker/opensandbox-server/
+!docker/opensandbox-server/opensandbox_capacity.py
+!docker/opensandbox-server/patch_opensandbox_server.py
+!docker/opensandbox-server/opensandbox_expirations.py
+!docker/opensandbox-server/opensandbox_mount_worker.py
+!docker/opensandbox-server/opensandbox_mount_recovery.py
+!docker/opensandbox-server/opensandbox_object_mounts.py
+!src/
+!src/backend/
+!src/backend/core/
+!src/backend/core/sandbox/
+!src/backend/core/sandbox/sessions/
+!src/backend/core/sandbox/sessions/metadata.py
+!src/backend/tests/
+!src/backend/tests/sandbox/
+!src/backend/tests/sandbox/test_opensandbox_controller_patch.py
diff --git a/docker/opensandbox-config.toml.tpl b/docker/opensandbox-server/opensandbox-config.toml.tpl
similarity index 100%
rename from docker/opensandbox-config.toml.tpl
rename to docker/opensandbox-server/opensandbox-config.toml.tpl
diff --git a/docker/opensandbox-server/opensandbox_capacity.py b/docker/opensandbox-server/opensandbox_capacity.py
new file mode 100644
index 00000000..8c1c0cbb
--- /dev/null
+++ b/docker/opensandbox-server/opensandbox_capacity.py
@@ -0,0 +1,31 @@
+"""Serialize Docker admission, not sandbox initialization or mount recovery."""
+import fcntl
+import hashlib
+import os
+from contextlib import contextmanager
+from pathlib import Path
+
+from fastapi import HTTPException
+
+
+@contextmanager
+def sandbox_capacity(service):
+ # File locking also fences multiple controller processes sharing the volume.
+ # Docker inventory survives controller/backend restart; no reservation ledger.
+ root = Path("/var/lib/opensandbox")
+ root.mkdir(parents=True, exist_ok=True)
+ namespace = hashlib.sha256(service.network_mode.encode()).hexdigest()[:24]
+ limit = int(os.environ.get("OPENSANDBOX_POOL_MAX_TOTAL", "20"))
+ if limit < 1:
+ raise HTTPException(503, "Sandbox capacity must be positive")
+ with (root / ("capacity-" + namespace + ".lock")).open("a") as lock:
+ try:
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError:
+ raise HTTPException(429, "Sandbox creation busy; retry later") from None
+ try:
+ if len(service._owned_sandboxes()) >= limit:
+ raise HTTPException(429, "Deployment sandbox capacity reached")
+ yield
+ finally:
+ fcntl.flock(lock, fcntl.LOCK_UN)
diff --git a/docker/opensandbox_expirations.py b/docker/opensandbox-server/opensandbox_expirations.py
similarity index 100%
rename from docker/opensandbox_expirations.py
rename to docker/opensandbox-server/opensandbox_expirations.py
diff --git a/docker/opensandbox_mount_recovery.py b/docker/opensandbox-server/opensandbox_mount_recovery.py
similarity index 91%
rename from docker/opensandbox_mount_recovery.py
rename to docker/opensandbox-server/opensandbox_mount_recovery.py
index b7efa6b0..09c608a2 100644
--- a/docker/opensandbox_mount_recovery.py
+++ b/docker/opensandbox-server/opensandbox_mount_recovery.py
@@ -55,7 +55,7 @@ def _specifications(service, container, request):
def _healthy(container, specs):
if not specs:
return True
- script = """import json,os,sys
+ script = """import errno,json,os,sys
from pathlib import Path
mounts = {}
for line in Path('/proc/self/mountinfo').read_text().splitlines():
@@ -63,14 +63,23 @@ def _healthy(container, specs):
separator = fields.index('-')
mounts[fields[4]] = (fields[separator + 1], fields[5].split(','))
for target, readonly in json.loads(sys.argv[1]):
+ if target not in mounts:
+ sys.exit(10)
filesystem, options = mounts[target]
- assert filesystem.startswith('fuse'), 'Object filesystem is absent'
- assert ('ro' in options) == readonly, 'Mount access mode differs'
- os.stat(target)
+ if not filesystem.startswith('fuse') or ('ro' in options) != readonly:
+ sys.exit(10)
+ try:
+ os.stat(target)
+ except OSError as exc:
+ sys.exit(10 if exc.errno == errno.ENOTCONN else 11)
"""
result = container.exec_run(["timeout", "4", "python3", "-c", script,
json.dumps([(s["target"], s["read_only"]) for s in specs])], user="0")
- return result.exit_code == 0
+ if result.exit_code == 0:
+ return True
+ if result.exit_code == 10:
+ return False
+ raise HTTPException(503, "Object mount health is uncertain; existing connection retained")
def remove_helper(service, sandbox_id):
from docker.errors import NotFound
@@ -127,7 +136,8 @@ def ensure_mounts(service, sandbox_id, request):
if api.exec_inspect(execution["Id"])["ExitCode"] != 0 or not _healthy(container, specs):
raise HTTPException(503, "Object mount recovery failed; workspace retained")
except Exception:
- remove_helper(service, sandbox_id)
+ # The helper may own live writes even if verification timed out.
+ # A later confirmed-broken probe can replace it safely.
raise
return {"epoch": EPOCH, "recovered": True}
diff --git a/docker/opensandbox_mount_worker.py b/docker/opensandbox-server/opensandbox_mount_worker.py
similarity index 100%
rename from docker/opensandbox_mount_worker.py
rename to docker/opensandbox-server/opensandbox_mount_worker.py
diff --git a/docker/opensandbox_object_mounts.py b/docker/opensandbox-server/opensandbox_object_mounts.py
similarity index 100%
rename from docker/opensandbox_object_mounts.py
rename to docker/opensandbox-server/opensandbox_object_mounts.py
diff --git a/docker/patch_opensandbox_server.py b/docker/opensandbox-server/patch_opensandbox_server.py
similarity index 96%
rename from docker/patch_opensandbox_server.py
rename to docker/opensandbox-server/patch_opensandbox_server.py
index 5460b21a..9bec849e 100644
--- a/docker/patch_opensandbox_server.py
+++ b/docker/opensandbox-server/patch_opensandbox_server.py
@@ -110,6 +110,8 @@ def patched_sources(root: Path) -> dict[str, str]:
" async def create_sandbox(self, request: CreateSandboxRequest) -> CreateSandboxResponse:\n"
" return await run_in_threadpool(self._create_sandbox_sync, request)\n\n"
" def _create_sandbox_sync(self, request: CreateSandboxRequest) -> CreateSandboxResponse:\n")
+ source = replace_once(source, "import inspect\n",
+ "import inspect\nfrom .capacity import sandbox_capacity\n")
sources[name] = source
name = "api/lifecycle.py"
@@ -152,6 +154,15 @@ def patched_sources(root: Path) -> dict[str, str]:
patch_shared_lifecycle(sources)
patch_durable_expirations(sources)
patch_namespace_mounts(sources)
+ name = "services/docker/docker_service.py"
+ source = sources[name]
+ start = source.index(" created_container = self._create_and_start_container(")
+ end = source.index("\n )", start) + len("\n )")
+ block = source[start:end]
+ source = (source[:start] + " with sandbox_capacity(self):\n"
+ + "\n".join(" " + line for line in block.splitlines()) + source[end:])
+ sources[name] = source
+
name = "services/docker/docker_service.py"
sources[name] = replace_once(sources[name], "import inspect\n",
"import inspect\nfrom .object_mounts import install as install_object_mounts\ninstall_object_mounts()\n")
diff --git a/docker/render-opensandbox-config.py b/docker/opensandbox-server/render-opensandbox-config.py
similarity index 100%
rename from docker/render-opensandbox-config.py
rename to docker/opensandbox-server/render-opensandbox-config.py
diff --git a/docker/Dockerfile.personal-agent b/docker/personal-agent/Dockerfile.personal-agent
similarity index 86%
rename from docker/Dockerfile.personal-agent
rename to docker/personal-agent/Dockerfile.personal-agent
index 3eecda26..b9106b79 100644
--- a/docker/Dockerfile.personal-agent
+++ b/docker/personal-agent/Dockerfile.personal-agent
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends xfce4-terminal
# Live screen for the owner: VNC on a private socket, behind a token-checked WebSocket.
RUN apt-get update && apt-get install -y --no-install-recommends x11vnc && rm -rf /var/lib/apt/lists/*
# A browser launcher with the flags this container needs, registered as the default browser.
-COPY docker/personal-agent-desktop/ /opt/personal-agent/desktop/
+COPY docker/personal-agent/desktop/ /opt/personal-agent/desktop/
RUN install -m 755 /opt/personal-agent/desktop/pa-browser /usr/local/bin/pa-browser \
&& install -m 644 /opt/personal-agent/desktop/pa-browser.desktop /usr/share/applications/pa-browser.desktop \
&& install -d /usr/share/xfce4/helpers \
@@ -17,13 +17,13 @@ RUN install -m 755 /opt/personal-agent/desktop/pa-browser /usr/local/bin/pa-brow
&& install -m 644 /opt/personal-agent/desktop/mimeapps.list /etc/xdg/mimeapps.list \
&& install -m 755 /opt/personal-agent/desktop/pa-stream.py /usr/local/bin/pa-stream
RUN useradd --create-home --uid 1001 --shell /bin/bash pa-desktop && chmod 700 /home/pa-desktop && mkdir -p /opt/personal-agent
-COPY src/backend/core/device_link/driver.py /opt/personal-agent/computer-driver.py
-COPY docker/personal-agent-entrypoint.sh /opt/personal-agent/start.sh
+COPY src/backend/personal_agent/device_link/driver.py /opt/personal-agent/computer-driver.py
+COPY docker/personal-agent/personal-agent-entrypoint.sh /opt/personal-agent/start.sh
RUN chmod 755 /opt/personal-agent/start.sh /opt/personal-agent/computer-driver.py
RUN find / -xdev -type f -perm /6000 -exec chmod a-s {} + 2>/dev/null && rm -f /etc/sudoers.d/ubuntu
# Lightweight client only. The cloud computer does not run another backend.
COPY src/backend/hugagent_cli /opt/hugagent-cli/hugagent_cli
-COPY docker/hugagent-cli-entrypoint.sh /usr/local/bin/hugagent
+COPY docker/personal-agent/hugagent-cli-entrypoint.sh /usr/local/bin/hugagent
RUN chmod 755 /usr/local/bin/hugagent
diff --git a/docker/compose.personal-agent.yml b/docker/personal-agent/compose.personal-agent.yml
similarity index 91%
rename from docker/compose.personal-agent.yml
rename to docker/personal-agent/compose.personal-agent.yml
index 86b8962a..d0afeb85 100644
--- a/docker/compose.personal-agent.yml
+++ b/docker/personal-agent/compose.personal-agent.yml
@@ -14,14 +14,14 @@ services:
PERSONAL_AGENT_OPENSANDBOX_API_KEY: ${PERSONAL_AGENT_OPENSANDBOX_API_KEY:?Set a private controller key}
PERSONAL_AGENT_COMPUTE_NETWORK: ${COMPOSE_PROJECT_NAME:-hugagent}_personal-agent-compute
volumes:
- - ./docker/render-personal-agent-config.py:/render.py:ro
+ - ./docker/personal-agent/render-personal-agent-config.py:/render.py:ro
- personal_agent_config:/out
command: ["python", "/render.py"]
personal-agent-opensandbox:
image: hugagent-opensandbox-server:v0.1.13-1
build:
context: .
- dockerfile: docker/Dockerfile.opensandbox-server
+ dockerfile: docker/opensandbox-server/Dockerfile.opensandbox-server
profiles: [personal-agent]
restart: unless-stopped
depends_on:
diff --git a/docker/personal-agent-desktop/helpers.rc b/docker/personal-agent/desktop/helpers.rc
similarity index 100%
rename from docker/personal-agent-desktop/helpers.rc
rename to docker/personal-agent/desktop/helpers.rc
diff --git a/docker/personal-agent-desktop/mimeapps.list b/docker/personal-agent/desktop/mimeapps.list
similarity index 100%
rename from docker/personal-agent-desktop/mimeapps.list
rename to docker/personal-agent/desktop/mimeapps.list
diff --git a/docker/personal-agent-desktop/pa-browser b/docker/personal-agent/desktop/pa-browser
similarity index 100%
rename from docker/personal-agent-desktop/pa-browser
rename to docker/personal-agent/desktop/pa-browser
diff --git a/docker/personal-agent-desktop/pa-browser-helper.desktop b/docker/personal-agent/desktop/pa-browser-helper.desktop
similarity index 100%
rename from docker/personal-agent-desktop/pa-browser-helper.desktop
rename to docker/personal-agent/desktop/pa-browser-helper.desktop
diff --git a/docker/personal-agent-desktop/pa-browser.desktop b/docker/personal-agent/desktop/pa-browser.desktop
similarity index 100%
rename from docker/personal-agent-desktop/pa-browser.desktop
rename to docker/personal-agent/desktop/pa-browser.desktop
diff --git a/docker/personal-agent-desktop/pa-stream.py b/docker/personal-agent/desktop/pa-stream.py
similarity index 100%
rename from docker/personal-agent-desktop/pa-stream.py
rename to docker/personal-agent/desktop/pa-stream.py
diff --git a/docker/hugagent-cli-entrypoint.sh b/docker/personal-agent/hugagent-cli-entrypoint.sh
similarity index 100%
rename from docker/hugagent-cli-entrypoint.sh
rename to docker/personal-agent/hugagent-cli-entrypoint.sh
diff --git a/docker/personal-agent-entrypoint.sh b/docker/personal-agent/personal-agent-entrypoint.sh
similarity index 94%
rename from docker/personal-agent-entrypoint.sh
rename to docker/personal-agent/personal-agent-entrypoint.sh
index 33865ada..dde1f819 100644
--- a/docker/personal-agent-entrypoint.sh
+++ b/docker/personal-agent/personal-agent-entrypoint.sh
@@ -31,4 +31,6 @@ su - pa-desktop -c 'export DISPLAY=:99 XAUTHORITY=/run/pa-desktop/.Xauthority; s
# Live screen: VNC listens only on a socket in the private directory; pa-stream checks the token.
su - pa-desktop -c 'sleep 2; while true; do x11vnc -display :99 -auth /run/pa-desktop/.Xauthority -rfbport 0 -unixsock /run/pa-desktop/vnc.sock -forever -shared -nopw -quiet -nowf -noscr -ncache 0 -cursor most -xkb -wait 10 -defer 10; sleep 1; done' >/run/pa-desktop/vnc.log 2>&1 &
su - pa-desktop -c 'while true; do /opt/python/current/bin/python3 -I /usr/local/bin/pa-stream; sleep 1; done' >/run/pa-desktop/stream.log 2>&1 &
-exec sleep infinity
+# PID 1 reaps every orphan and kills what task commands (UID 1000) leave behind;
+# the desktop above runs as UID 1001 and is only reaped.
+exec sandbox-init 1000
diff --git a/docker/render-personal-agent-config.py b/docker/personal-agent/render-personal-agent-config.py
similarity index 100%
rename from docker/render-personal-agent-config.py
rename to docker/personal-agent/render-personal-agent-config.py
diff --git a/docker/Dockerfile.opensandbox b/docker/sandbox/Dockerfile.opensandbox
similarity index 92%
rename from docker/Dockerfile.opensandbox
rename to docker/sandbox/Dockerfile.opensandbox
index b84926ab..957ebb3f 100644
--- a/docker/Dockerfile.opensandbox
+++ b/docker/sandbox/Dockerfile.opensandbox
@@ -5,7 +5,7 @@
#
# 构建:
# docker build -t hugagent-opensandbox-custom:latest \
-# -f Dockerfile.opensandbox .
+# -f docker/sandbox/Dockerfile.opensandbox .
#
# 启用:在 .env 中设置
# OPENSANDBOX_IMAGE=hugagent-opensandbox-custom:latest
@@ -69,7 +69,7 @@ RUN /opt/node/v22.2.0/bin/npm install -g @larksuite/cli@1.0.78 @larksuite/whiteb
RUN /opt/node/v22.2.0/bin/npm install -g openyida@2026.7.12-1
# ─── Python 数据/文档/网络全家桶 ───────────────────────────────────
-COPY docker/requirements-script-runner.txt /tmp/req-script-runner.txt
+COPY docker/sandbox/requirements-script-runner.txt /tmp/req-script-runner.txt
# Interactive package workers use FastAPI/uvicorn inside the sandbox.
RUN $PY_BIN -m pip install --no-cache-dir -r /tmp/req-script-runner.txt \
&& rm /tmp/req-script-runner.txt
@@ -77,7 +77,7 @@ RUN $PY_BIN -m pip install --no-cache-dir -r /tmp/req-script-runner.txt \
# Skill 依赖(由 backend admin UI 触发 rebuild 前聚合写入 requirements-skills.txt)。
# 依赖**只**在此 bake 进镜像;沙盒启动不再跑 pip。新增依赖须重新构建本镜像
# (管理台 → 沙盒依赖 → 重建)。/opt/skill-deps/ 副本仅留作排查参考。
-COPY docker/requirements-skills.txt /opt/skill-deps/requirements-skills.txt
+COPY docker/sandbox/requirements-skills.txt /opt/skill-deps/requirements-skills.txt
RUN $PY_BIN -m pip install --no-cache-dir -r /opt/skill-deps/requirements-skills.txt || \
(echo "[opensandbox] skill-deps install failed; continuing with empty set"; true)
@@ -141,17 +141,17 @@ RUN HIMALAYA_ARCH="$([ "${TARGETARCH:-amd64}" = "arm64" ] && echo aarch64 || ech
# 目录、node_modules 硬链接物化到 /workspace 临时区(秒级、离线、不进 myspace)。
# 构建期跑一次 smoke build:模板坏了在镜像构建阶段立刻暴露,而不是用户对话里。
# manifest 单独成层先 npm ci:模板源码微调不再击穿依赖层缓存(lock 不变即命中)
-COPY docker/site-template/react-vite/package.json \
- docker/site-template/react-vite/package-lock.json \
- docker/site-template/react-vite/.npmrc \
+COPY docker/sandbox/site-template/react-vite/package.json \
+ docker/sandbox/site-template/react-vite/package-lock.json \
+ docker/sandbox/site-template/react-vite/.npmrc \
/opt/site-template/react-vite/
RUN cd /opt/site-template/react-vite \
&& export PATH=/opt/node/v22.2.0/bin:$PATH \
&& npm ci --no-audit --no-fund \
&& npm cache clean --force \
&& rm -rf /root/.npm
-COPY docker/site-template/init-react-site.sh /opt/site-template/init-react-site.sh
-COPY docker/site-template/react-vite/ /opt/site-template/react-vite/
+COPY docker/sandbox/site-template/init-react-site.sh /opt/site-template/init-react-site.sh
+COPY docker/sandbox/site-template/react-vite/ /opt/site-template/react-vite/
RUN cd /opt/site-template/react-vite \
&& export PATH=/opt/node/v22.2.0/bin:$PATH \
&& SITE_DIST=/tmp/site-smoke SITE_CACHE=/tmp/site-cache npm run build \
@@ -221,5 +221,14 @@ RUN mkdir -p /home/ubuntu/.local /home/ubuntu/.jupyter /home/ubuntu/.npm \
done
# Real launch detects driver/browser revision mismatches before delivery.
-COPY docker/verify-browser-runtime.py /opt/verify-browser-runtime.py
+COPY docker/sandbox/verify-browser-runtime.py /opt/verify-browser-runtime.py
RUN runuser -u ubuntu -- $PY_BIN /opt/verify-browser-runtime.py --container-isolated
+
+# ─── sandbox-init(PID 1 + 命令的 subreaper)─────────────────────────────
+# 由 provider 的 entrypoint 作为 PID 1 启动:回收所有孤儿进程,并杀掉沙盒用户
+# (SANDBOX_RUN_UID)在命令结束后留下的进程;每条用户命令经 `sandbox-init
+# --subreaper` 运行,setsid / 两次 fork 也逃不出命令的进程树。放在末尾,
+# 改动不击穿上面的大层缓存。
+COPY docker/sandbox/sandbox-init.c /tmp/sandbox-init.c
+RUN gcc -O2 -Wall -Werror -o /usr/local/bin/sandbox-init /tmp/sandbox-init.c \
+ && rm /tmp/sandbox-init.c
diff --git a/docker/Dockerfile.script-runner b/docker/sandbox/Dockerfile.script-runner
similarity index 96%
rename from docker/Dockerfile.script-runner
rename to docker/sandbox/Dockerfile.script-runner
index 40f0103a..f921e735 100644
--- a/docker/Dockerfile.script-runner
+++ b/docker/sandbox/Dockerfile.script-runner
@@ -4,7 +4,7 @@
#
# 构建:
# docker build -t hugagent-script-runner:latest \
-# -f Dockerfile.script-runner .
+# -f docker/sandbox/Dockerfile.script-runner .
# 由 docker-compose 在 SANDBOX_PROVIDER=script_runner(COMPOSE_PROFILES=script_runner)
# 时自动构建启动。
@@ -77,13 +77,13 @@ RUN HIMALAYA_ARCH="$([ "${TARGETARCH:-amd64}" = "arm64" ] && echo aarch64 || ech
fi && rm -f /tmp/himalaya.tgz
# Python deps
-COPY docker/requirements-script-runner.txt /tmp/
+COPY docker/sandbox/requirements-script-runner.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements-script-runner.txt \
&& rm /tmp/requirements-script-runner.txt
# Skill 依赖(由 backend admin UI 触发 rebuild 前聚合写入 requirements-skills.txt)。
# 文件可能为空(默认占位),pip 接受空 requirements,不会失败。
-COPY docker/requirements-skills.txt /opt/skill-deps/requirements-skills.txt
+COPY docker/sandbox/requirements-skills.txt /opt/skill-deps/requirements-skills.txt
RUN pip install --no-cache-dir -r /opt/skill-deps/requirements-skills.txt || \
(echo "[script-runner] skill-deps install failed; continuing with empty set"; true)
@@ -141,5 +141,5 @@ EXPOSE 8900
CMD ["python", "-m", "uvicorn", "core.sandbox.runner.server:app", "--host", "0.0.0.0", "--port", "8900"]
# Real launch detects driver/browser revision mismatches before delivery.
-COPY docker/verify-browser-runtime.py /opt/verify-browser-runtime.py
+COPY docker/sandbox/verify-browser-runtime.py /opt/verify-browser-runtime.py
RUN python3 /opt/verify-browser-runtime.py --container-isolated
diff --git a/docker/apt-skills.txt b/docker/sandbox/apt-skills.txt
similarity index 100%
rename from docker/apt-skills.txt
rename to docker/sandbox/apt-skills.txt
diff --git a/docker/package-skills.json b/docker/sandbox/package-skills.json
similarity index 100%
rename from docker/package-skills.json
rename to docker/sandbox/package-skills.json
diff --git a/docker/requirements-script-runner.txt b/docker/sandbox/requirements-script-runner.txt
similarity index 100%
rename from docker/requirements-script-runner.txt
rename to docker/sandbox/requirements-script-runner.txt
diff --git a/docker/requirements-skills.txt b/docker/sandbox/requirements-skills.txt
similarity index 100%
rename from docker/requirements-skills.txt
rename to docker/sandbox/requirements-skills.txt
diff --git a/docker/sandbox/sandbox-init.c b/docker/sandbox/sandbox-init.c
new file mode 100644
index 00000000..39224bf0
--- /dev/null
+++ b/docker/sandbox/sandbox-init.c
@@ -0,0 +1,78 @@
+/*
+ * PID 1 of an OpenSandbox container, and the subreaper every user command runs under.
+ *
+ * sandbox-init as PID 1: reap every child, and SIGKILL the ones
+ * running as
+ * sandbox-init --subreaper cmd ... become a child subreaper, then exec cmd
+ *
+ * User commands run as under --subreaper, so whatever they start stays in their
+ * subtree for as long as they run, however it detaches (setsid, double fork). A leftover
+ * only reaches PID 1 once its command has ended, and is killed there: no process of
+ * outlives the command that started it. Sandbox services run as root and are
+ * only reaped.
+ */
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* The real uid: a process cannot change it, unlike the owner of /proc/. */
+static long real_uid(long pid)
+{
+ char path[64], line[256];
+ long uid = -1;
+ snprintf(path, sizeof path, "/proc/%ld/status", pid);
+ FILE *status = fopen(path, "r");
+ if (!status)
+ return -1;
+ while (fgets(line, sizeof line, status))
+ if (sscanf(line, "Uid: %ld", &uid) == 1)
+ break;
+ fclose(status);
+ return uid;
+}
+
+static void kill_orphans(long uid)
+{
+ /* Nothing is reaped during the scan, so no PID read here can be reused before its kill. */
+ FILE *children = fopen("/proc/1/task/1/children", "r");
+ long pid;
+ if (!children)
+ return;
+ while (fscanf(children, "%ld", &pid) == 1)
+ if (real_uid(pid) == uid)
+ kill((pid_t)pid, SIGKILL);
+ fclose(children);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc > 2 && strcmp(argv[1], "--subreaper") == 0) {
+ if (prctl(PR_SET_CHILD_SUBREAPER, 1) == 0)
+ execvp(argv[2], argv + 2);
+ perror("sandbox-init");
+ return 127;
+ }
+ if (argc != 2) {
+ fprintf(stderr, "usage: sandbox-init | sandbox-init --subreaper cmd ...\n");
+ return 2;
+ }
+ long uid = strtol(argv[1], NULL, 10);
+ sigset_t chld;
+ sigemptyset(&chld);
+ sigaddset(&chld, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &chld, NULL);
+ /* An orphan is adopted without any signal, so look for leftovers every second too. */
+ const struct timespec tick = {1, 0};
+ for (;;) {
+ sigtimedwait(&chld, NULL, &tick);
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+ kill_orphans(uid);
+ }
+}
diff --git a/docker/site-template/init-react-site.sh b/docker/sandbox/site-template/init-react-site.sh
similarity index 100%
rename from docker/site-template/init-react-site.sh
rename to docker/sandbox/site-template/init-react-site.sh
diff --git a/docker/site-template/react-vite/.npmrc b/docker/sandbox/site-template/react-vite/.npmrc
similarity index 100%
rename from docker/site-template/react-vite/.npmrc
rename to docker/sandbox/site-template/react-vite/.npmrc
diff --git a/docker/site-template/react-vite/index.html b/docker/sandbox/site-template/react-vite/index.html
similarity index 100%
rename from docker/site-template/react-vite/index.html
rename to docker/sandbox/site-template/react-vite/index.html
diff --git a/docker/site-template/react-vite/package-lock.json b/docker/sandbox/site-template/react-vite/package-lock.json
similarity index 100%
rename from docker/site-template/react-vite/package-lock.json
rename to docker/sandbox/site-template/react-vite/package-lock.json
diff --git a/docker/site-template/react-vite/package.json b/docker/sandbox/site-template/react-vite/package.json
similarity index 100%
rename from docker/site-template/react-vite/package.json
rename to docker/sandbox/site-template/react-vite/package.json
diff --git a/docker/site-template/react-vite/src/App.jsx b/docker/sandbox/site-template/react-vite/src/App.jsx
similarity index 100%
rename from docker/site-template/react-vite/src/App.jsx
rename to docker/sandbox/site-template/react-vite/src/App.jsx
diff --git a/docker/site-template/react-vite/src/index.css b/docker/sandbox/site-template/react-vite/src/index.css
similarity index 100%
rename from docker/site-template/react-vite/src/index.css
rename to docker/sandbox/site-template/react-vite/src/index.css
diff --git a/docker/site-template/react-vite/src/lib/siteApi.js b/docker/sandbox/site-template/react-vite/src/lib/siteApi.js
similarity index 100%
rename from docker/site-template/react-vite/src/lib/siteApi.js
rename to docker/sandbox/site-template/react-vite/src/lib/siteApi.js
diff --git a/docker/site-template/react-vite/src/main.jsx b/docker/sandbox/site-template/react-vite/src/main.jsx
similarity index 100%
rename from docker/site-template/react-vite/src/main.jsx
rename to docker/sandbox/site-template/react-vite/src/main.jsx
diff --git a/docker/site-template/react-vite/src/pages/Home.jsx b/docker/sandbox/site-template/react-vite/src/pages/Home.jsx
similarity index 100%
rename from docker/site-template/react-vite/src/pages/Home.jsx
rename to docker/sandbox/site-template/react-vite/src/pages/Home.jsx
diff --git a/docker/site-template/react-vite/vite.config.mjs b/docker/sandbox/site-template/react-vite/vite.config.mjs
similarity index 100%
rename from docker/site-template/react-vite/vite.config.mjs
rename to docker/sandbox/site-template/react-vite/vite.config.mjs
diff --git a/docker/verify-browser-runtime.py b/docker/sandbox/verify-browser-runtime.py
similarity index 100%
rename from docker/verify-browser-runtime.py
rename to docker/sandbox/verify-browser-runtime.py
diff --git a/document/README.md b/document/README.md
index 90158198..f06d0e45 100644
--- a/document/README.md
+++ b/document/README.md
@@ -1,10 +1,10 @@
# HugAgentOS 文档 / Documentation
-> 最后更新:2026-07-21
+> 最后更新 / Last updated:2026-10-23
-HugAgentOS 是一个企业级 AI Agent 平台(FastAPI + React + AgentScope 2.0),以 open-core 模式提供 **开源社区版(CE)** 与 **商业版(EE)** 双形态。产品文档按语言分树维护:
+HugAgentOS 是一个开源、可自托管的 Agent Harness:你提出的问题在云端持续执行,关掉电脑也不会中断;在此之上提供 7×24 小时在线的 Personal Agent,并以 HugAgentOS Cloud(服务器)和 HugAgentOS Local(桌面本机)两种形态运行,以 open-core 模式提供 **开源社区版(CE)** 与 **商业版(EE)**。产品文档按语言分树维护:
-HugAgentOS is an enterprise-grade AI agent platform (FastAPI + React + AgentScope 2.0), shipped in an open-core model as a **Community Edition (CE)** and an **Enterprise Edition (EE)**. Product documentation is maintained per language:
+HugAgentOS is an open-source, self-hosted agent harness: your request keeps running in the cloud even after you close your computer. On top of it, HugAgentOS provides an always-on Personal Agent and runs as HugAgentOS Cloud (on your server) or HugAgentOS Local (on your desktop), shipped in an open-core model as a **Community Edition (CE)** and an **Enterprise Edition (EE)**. Product documentation is maintained per language:
| 语言 / Language | 入口 / Entry |
|---|---|
@@ -19,14 +19,13 @@ HugAgentOS is an enterprise-grade AI agent platform (FastAPI + React + AgentScop
document/
├── zh-CN/ · en/ # 双语产品文档(镜像)
│ ├── getting-started/ # 产品简介、快速开始、领域本体快速入门
-│ ├── deployment/ # 部署总览(选型) + 无 Docker 一键安装 / Docker Compose / 离线生产 / Windows(WSL2) / 环境变量参考
+│ ├── deployment/ # 部署总览、Docker Compose、桌面本机模式、一键安装、Windows / macOS / UOS、环境变量
│ ├── architecture/ # 总体架构、后端、前端、数据模型
-│ ├── modules/ # 17 个功能模块文档(对话/提示词/能力目录/模型/
-│ │ # MCP 工具/技能/沙箱/记忆/知识库/存储/项目空间/
-│ │ # 认证/管理台/自动化/画布与产物)
-│ ├── api/ # API 总览(信封/鉴权/SSE/路由清单)、错误码
+│ ├── modules/ # 功能模块:对话与 Run、Personal Agent、自主循环、后台作业、MCP、技能、沙箱、
+│ │ # 记忆、本体治理、知识库、站点与应用托管、存储、项目空间、认证、管理台等
+│ ├── api/ # API 总览(信封 / 鉴权 / SSE / 路由清单)、错误码
│ ├── editions/ # 社区版 vs 商业版、License 机制、CE 构建管线
-│ └── development/ # 后端 / 前端开发指南
+│ └── development/ # 后端 / 前端开发、界面定制、工作台(Workbench)协议与 SDK
└── internal/ # ⚠️ 内部文档(仅主仓,不随社区版分发)
├── agents/ # 开发工作流约定(development-flow / issue-tracker /
│ # triage-labels / domain),被 .agents/skills 与 CLAUDE.md 引用
@@ -44,151 +43,13 @@ document/
`zh-CN/`, `en/`, and this index are **public product docs**, shipped with the Community Edition derived tree; EE-only capabilities are explicitly labeled. `internal/` holds **internal documents** (business strategy, split blueprints, design drafts) and is excluded from the CE tree via `ce/manifest.yaml` — put any new internal document there.
-云文档在线编辑:Markdown、TXT 和代码文件的编辑、保存、权限与版本冲突处理见
-[我的空间](zh-CN/modules/projects-myspace.md#云文档在线编辑) /
-[Online cloud document editing](en/modules/projects-myspace.md#online-cloud-document-editing)。
+## 常用专题 / Frequently referenced topics
-工作台协议 / Workbench protocol:
-[中文接入说明](zh-CN/development/workbench-protocol.md) /
-[English integration guide](en/development/workbench-protocol.md)。
-
-普通外部应用:[中文](zh-CN/development/workbench-external-apps.md) · [English](en/development/workbench-external-apps.md)。
-
-工作台目录与布局策略:[中文](zh-CN/development/workbench-access-policies.md) · [English](en/development/workbench-access-policies.md)。
-
-工作台导航偏好:[中文](zh-CN/development/workbench-navigation-preferences.md) · [English](en/development/workbench-navigation-preferences.md)。
-
-独立 SDK 与插件页面:[中文](zh-CN/development/workbench-sdk.md) · [English](en/development/workbench-sdk.md)。
-
-工作台独立工程与权限预览:[中文](zh-CN/development/workbench-preview.md) · [English](en/development/workbench-preview.md)。
-工作台管理员指南:[中文](zh-CN/development/workbench-administration.md) · [English](en/development/workbench-administration.md)。
-工作台错误码:[中文](zh-CN/development/workbench-errors.md) · [English](en/development/workbench-errors.md)。
-工作台兼容与迁移:[中文](zh-CN/development/workbench-migration.md) · [English](en/development/workbench-migration.md)。
-
-工作台目录通知与撤销:[中文](zh-CN/development/workbench-invalidation.md) · [English](en/development/workbench-invalidation.md)。
-
-- [工作台构建与交付](zh-CN/development/workbench-build.md)
-
-- [workbench-sdk](en/development/workbench-sdk.md)
-
-- [workbench-access-policies](en/development/workbench-access-policies.md)
-
-- [workbench-external-apps](en/development/workbench-external-apps.md)
-
-- [workbench-plugin-upgrades](en/development/workbench-plugin-upgrades.md)
-
-- [workbench-protocol](en/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](en/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](zh-CN/development/workbench-sdk.md)
-
-- [workbench-access-policies](zh-CN/development/workbench-access-policies.md)
-
-- [workbench-external-apps](zh-CN/development/workbench-external-apps.md)
-
-- [workbench-plugin-upgrades](zh-CN/development/workbench-plugin-upgrades.md)
-
-- [workbench-protocol](zh-CN/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](zh-CN/development/workbench-navigation-preferences.md)
-
-- [workbench-origins](en/development/workbench-origins.md)
-
-- [workbench-sdk](en/development/workbench-sdk.md)
-
-- [workbench-access-policies](en/development/workbench-access-policies.md)
-
-- [workbench-external-apps](en/development/workbench-external-apps.md)
-
-- [workbench-protocol](en/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](en/development/workbench-navigation-preferences.md)
-
-- [workbench-origins](zh-CN/development/workbench-origins.md)
-
-- [workbench-sdk](zh-CN/development/workbench-sdk.md)
-
-- [workbench-access-policies](zh-CN/development/workbench-access-policies.md)
-
-- [workbench-external-apps](zh-CN/development/workbench-external-apps.md)
-
-- [workbench-protocol](zh-CN/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](zh-CN/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](en/development/workbench-sdk.md)
-
-- [workbench-platform-adapters](en/development/workbench-platform-adapters.md)
-
-- [workbench-access-policies](en/development/workbench-access-policies.md)
-
-- [workbench-external-apps](en/development/workbench-external-apps.md)
-
-- [workbench-protocol](en/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](en/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](zh-CN/development/workbench-sdk.md)
-
-- [workbench-platform-adapters](zh-CN/development/workbench-platform-adapters.md)
-
-- [workbench-access-policies](zh-CN/development/workbench-access-policies.md)
-
-- [workbench-external-apps](zh-CN/development/workbench-external-apps.md)
-
-- [workbench-protocol](zh-CN/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](zh-CN/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](en/development/workbench-sdk.md)
-
-- [workbench-access-policies](en/development/workbench-access-policies.md)
-
-- [workbench-external-apps](en/development/workbench-external-apps.md)
-
-- [workbench-activity](en/development/workbench-activity.md)
-
-- [workbench-protocol](en/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](en/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](zh-CN/development/workbench-sdk.md)
-
-- [workbench-access-policies](zh-CN/development/workbench-access-policies.md)
-
-- [workbench-external-apps](zh-CN/development/workbench-external-apps.md)
-
-- [workbench-activity](zh-CN/development/workbench-activity.md)
-
-- [workbench-protocol](zh-CN/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](zh-CN/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](en/development/workbench-sdk.md)
-
-- [workbench-access-policies](en/development/workbench-access-policies.md)
-
-- [workbench-external-apps](en/development/workbench-external-apps.md)
-
-- [workbench-diagnostics](en/development/workbench-diagnostics.md)
-
-- [workbench-protocol](en/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](en/development/workbench-navigation-preferences.md)
-
-- [workbench-sdk](zh-CN/development/workbench-sdk.md)
-
-- [workbench-access-policies](zh-CN/development/workbench-access-policies.md)
-
-- [workbench-external-apps](zh-CN/development/workbench-external-apps.md)
-
-- [workbench-diagnostics](zh-CN/development/workbench-diagnostics.md)
-
-- [workbench-protocol](zh-CN/development/workbench-protocol.md)
-
-- [workbench-navigation-preferences](zh-CN/development/workbench-navigation-preferences.md)
-
-项目与智能体独立对话链接 / Standalone project and agent chat links:[中文](zh-CN/modules/chat.md#项目和智能体的独立对话链接) · [English](en/modules/chat.md#standalone-chat-links-for-projects-and-agents)。
-
-模块页面滚动与应用中心:[中文](zh-CN/architecture/frontend.md#模块页面滚动与应用中心) · [English](en/architecture/frontend.md#module-page-scrolling-and-app-center)。
+| 专题 / Topic | 中文 | English |
+|---|---|---|
+| Personal Agent | [Personal Agent](zh-CN/modules/personal-agent.md) | [Personal Agent](en/modules/personal-agent.md) |
+| 桌面本机与混合模式 / Desktop local and hybrid modes | [桌面离线本机模式](zh-CN/deployment/desktop-local-mode.md) | [Desktop local mode](en/deployment/desktop-local-mode.md) |
+| 云文档在线编辑 / Online cloud document editing | [我的空间](zh-CN/modules/projects-myspace.md#云文档在线编辑) | [Projects & My Space](en/modules/projects-myspace.md#online-cloud-document-editing) |
+| 项目与智能体独立对话链接 / Standalone chat links | [对话](zh-CN/modules/chat.md#项目和智能体的独立对话链接) | [Chat](en/modules/chat.md#standalone-chat-links-for-projects-and-agents) |
+| 模块页面滚动与应用中心 / Module scrolling and app center | [前端架构](zh-CN/architecture/frontend.md#模块页面滚动与应用中心) | [Frontend](en/architecture/frontend.md#module-page-scrolling-and-app-center) |
+| 工作台协议与 SDK / Workbench protocol and SDK | [工作台文档](zh-CN/README.md#开发指南) | [Workbench docs](en/README.md#development) |
diff --git a/document/en/README.md b/document/en/README.md
index b2d3aa13..6c80cf3b 100644
--- a/document/en/README.md
+++ b/document/en/README.md
@@ -1,15 +1,19 @@
# HugAgentOS Documentation
-> Last updated: 2026-07-21 | [简体中文](../zh-CN/README.md)
+> Last updated: 2026-10-23 | [简体中文](../zh-CN/README.md)
-HugAgentOS is an enterprise-grade AI agent platform: a FastAPI backend, a React frontend, and an AgentScope 2.0 agent runtime, fully containerized with Docker and shipped in an open-core model as a **Community Edition (CE)** and an **Enterprise Edition (EE)**. This tree is the complete product documentation for operators, users, and integrators.
+HugAgentOS is an open-source, self-hosted agent harness: your request keeps running in the cloud
+even after you close your computer. On top of it, HugAgentOS provides an always-on Personal Agent
+and runs as HugAgentOS Cloud (on your server) or HugAgentOS Local (on your desktop). It ships as an
+open-source Community Edition (CE) and an Enterprise Edition (EE). This tree is the complete
+product documentation for operators, users, and integrators.
## Getting Started
| Document | Description |
|------|------|
-| [Introduction](getting-started/introduction.md) | What it is, feature overview, CE/EE at a glance, tech stack and architecture |
-| [Quick Start](getting-started/quick-start.md) | Run HugAgentOS with the one-command installer or Docker Compose |
+| [Introduction](getting-started/introduction.md) | The durable agent, runtime forms, feature overview, tech stack and architecture |
+| [Quick Start](getting-started/quick-start.md) | Choose HugAgentOS Cloud, the desktop client or the one-command installer, and enable Personal Agent |
| [Build a Domain Ontology Quickly](getting-started/domain-ontology-quickstart.md) | Validate, publish, test, and bind tools with a minimal Domain Pack |
## Deployment
@@ -19,42 +23,74 @@ Start with the [Deployment Guide (choosing a method)](deployment/README.md), the
| Document | Description |
|------|------|
| [Deployment Guide · Overview](deployment/README.md) | Comparison and selection of deployment methods, post-deployment verification |
-| [No-Docker Quick Install](deployment/quick-install.md) | Zero-dependency single machine: one command, SQLite + in-process event log/ephemeral state + subprocess MCP/sandbox |
-| [Docker Compose Deployment](deployment/docker-compose.md) | Team/production standard: full service topology, profiles, rebuild workflows, database migrations |
-| [Offline Production Deployment](deployment/offline-production.md) | Isolated environments: image tarball packaging / production-side loading / prompt snapshot migration (Enterprise Edition) |
-| [Windows Deployment](deployment/windows-deployment.md) | Docker Desktop / WSL2 deltas, line endings, paths and sandbox limitations |
+| [Docker Compose Deployment](deployment/docker-compose.md) | The standard HugAgentOS Cloud setup: service topology, profiles, rebuild workflows, database migrations |
+| [Desktop Local Mode](deployment/desktop-local-mode.md) | HugAgentOS Local: private runtime, local-only / cloud-only / local + cloud, upgrades and rollback |
+| [Local Project Workspace](deployment/local-project-workspace.md) | Execution directory and permission rules for bound local projects |
+| [Local and Cloud Skills and Plugins](deployment/local-capability-management.md) | Where skills and plugins come from and how they are managed in hybrid mode |
+| [No-Docker Quick Install](deployment/quick-install.md) | Single machine: SQLite + in-process state + subprocess MCP / sandbox |
+| [Windows Deployment](deployment/windows-deployment.md) | Desktop-hosted single-machine service or Docker Desktop + WSL2 |
+| [macOS Signing and Upgrades](deployment/macos-signing.md) | Desktop signing, notarization and upgrades |
+| [My Space File Mounts](deployment/space-files.md) | The file directories shared by the web app and sandboxes |
| [Environment Variables](deployment/environment-variables.md) | Complete variable reference (defaults / purpose / CE·EE relevance) |
## Architecture
| Document | Description |
|------|------|
-| [Overview](architecture/overview.md) | Layered architecture, full request lifecycle, container topology, key design decisions |
-| [Backend](architecture/backend.md) | Full `src/backend/` walkthrough: api / orchestration / 15 core submodules / router registry |
-| [Frontend](architecture/frontend.md) | Five app entries, component groups, Zustand stores, hooks, build chain |
-| [Data Model](architecture/data-model.md) | 76 tables by domain, dual alembic chains, CE/EE table boundary |
+| [Overview](architecture/overview.md) | Layered architecture, ontology control plane, full request lifecycle, container topology |
+| [Backend](architecture/backend.md) | `src/backend/` walkthrough and router registry |
+| [Frontend](architecture/frontend.md) | App entries, component groups, Zustand stores, module pages and the app center |
+| [Data Model](architecture/data-model.md) | Tables by domain, migration chains, CE/EE table boundary |
## Modules
+**Harness and long-running work**
+
+| Document | Description |
+|------|------|
+| [Chat & Orchestration](modules/chat.md) | Runs decoupled from connections, stream resume, mid-run steering, per-step context restore and compaction, plan mode, sub-agents |
+| [Personal Agent](modules/personal-agent.md) | The 24/7 personal agent: background tasks, permanent cloud computer, schedules and heartbeats, IM channels, approvals, file memory and Dreaming |
+| [Autonomous Loop](modules/autonomous-loop.md) | Goals that survive across turns, maker ≠ checker, stall guards, resumable runs |
+| [Workflow Background Jobs](modules/workflow-jobs.md) | Managed sandbox processes, lease takeover, derived main agents |
+| [Automation & Plan Mode](modules/automation.md) | Scheduled tasks and plan mode |
+
+**Capabilities and extensions**
+
| Document | Description |
|------|------|
-| [Chat & Orchestration](modules/chat.md) | End-to-end chat flow, SSE events, citations, plan mode, sub-agents, stream resume |
-| [Prompt System](modules/prompts.md) | Assembly precedence, version pool, prompt hub, cross-environment migration |
+| [MCP Tool System](modules/mcp-tools.md) | Built-in and plugin servers, connection pooling, custom MCP |
+| [Agent Skills](modules/agent-skills.md) | SKILL.md mechanics, bundle layering, skill marketplace |
+| [Browser Automation Plugin](modules/browser-automation.md) | Browse pages, operate forms and test web pages |
| [Capability Catalog](modules/catalog.md) | Catalog as single source of truth, capability gating, self-service capabilities |
-| [Model Providers](modules/model-providers.md) | Provider & role system, dynamic switching, personal API keys, billing |
-| [MCP Tool System](modules/mcp-tools.md) | Built-in and plugin servers, connection pooling, admin/user custom MCP |
-| [Agent Skills](modules/agent-skills.md) | SKILL.md mechanics, bundle layering, skill marketplace, distillation |
-| [Sandbox Execution](modules/sandbox.md) | Three providers, bash tools, snapshot persistence, MySpace bind-mount |
-| [Memory System](modules/memory.md) | L1/L2/L3 layered memory, sanitization, audit, mem0 infrastructure |
-| [Domain Ontology Governance and Validation](modules/ontology-harness.md) | CE Settings governance, Domain Packs, tool gates, pre-delivery review, and governed evolution |
-| [Knowledge Base](modules/knowledge-base.md) | Self-hosted KB (hybrid retrieval) and Dify integration, public KB admin |
-| [Knowledge Base Wiki](modules/knowledge-base-wiki.md) | LLM-extracted entities and concepts as interlinked pages and a concept graph |
-| [Object Storage](modules/storage.md) | local / s3 / oss backends, artifact store, file pipelines |
-| [Projects & My Space](modules/projects-myspace.md) | Project workspaces, personal/team folders, files into context |
-| [Authentication & Permissions](modules/auth.md) | Three AUTH_MODEs, SSO, permission bits, admin credentials |
-| [Admin Consoles](modules/admin-console.md) | The /admin and /config consoles, 19 admin route groups |
-| [Automation & Batch](modules/automation.md) | Scheduled tasks, plan mode, batch orchestration |
+| [Sandbox Execution](modules/sandbox.md) | Providers, Bash tools, snapshot persistence, My Space mounts |
+| [Model Providers](modules/model-providers.md) | Provider and role system, dynamic switching, personal API keys |
+| [Prompt System](modules/prompts.md) | Assembly precedence, version pool, cross-environment migration |
+
+**Knowledge, memory and governance**
+
+| Document | Description |
+|------|------|
+| [Memory System](modules/memory.md) | L1/L2/L3 layered memory, durable outbox, procedural learning, sanitization and audit |
+| [Domain Ontology Governance and Validation](modules/ontology-harness.md) | Domain Packs, tool gates, pre-delivery review, human review of evolution drafts |
+| [Knowledge Base](modules/knowledge-base.md) | Self-hosted knowledge bases (hybrid retrieval) and external sources |
+| [Knowledge Base Wiki](modules/knowledge-base-wiki.md) | Extracted entities and concepts as interlinked pages and a concept graph |
+
+**Workspaces and artifacts**
+
+| Document | Description |
+|------|------|
+| [Projects & My Space](modules/projects-myspace.md) | Project workspaces, personal folders, online cloud document editing, files into context |
| [Canvas & Artifacts](modules/canvas-artifacts.md) | Univer canvas, code artifacts, artifact center, chat sharing |
+| [Sites](modules/sites.md) | Build sites in chat and host them on the platform |
+| [Application Databases and MCP Hosting](modules/application-hosting.md) | Separate application databases, form data and MCP publishing |
+| [Object Storage](modules/storage.md) | Local and S3-compatible storage, artifact store, file pipelines |
+
+**Platform administration**
+
+| Document | Description |
+|------|------|
+| [Authentication & Permissions](modules/auth.md) | AUTH_MODE, SSO, permission bits, admin credentials |
+| [Admin Consoles](modules/admin-console.md) | The /admin and /config consoles |
## API Reference
@@ -67,21 +103,40 @@ Start with the [Deployment Guide (choosing a method)](deployment/README.md), the
| Document | Description |
|------|------|
-| [Community vs Enterprise](editions/overview.md) | Open-core model, feature matrix, three runtime modes, upgrade paths |
-| [License Mechanism](editions/license.md) | Offline signature verification, state machine, feature enforcement, issuing tool (EE) |
+| [Community vs Enterprise](editions/overview.md) | Open-core model, feature matrix, runtime forms, upgrade paths |
+| [License Mechanism](editions/license.md) | Offline signature verification, state machine, feature enforcement (EE) |
| [CE Build Pipeline](editions/build-ce.md) | Manifest, build_ce.py pipeline, overlay, acceptance gates |
## Development
| Document | Description |
|------|------|
-| [Backend Development](development/backend.md) | In-Docker dev model, tests, migrations, layering conventions, adding routes/MCP/skills |
-| [Frontend Development](development/frontend.md) | Build hot-swap, directory conventions, API call conventions, edition gating |
-
----
-
-Internal design and planning documents (CE/EE split blueprints, sandbox snapshot design, etc.) live in the [docs root](../README.md).
+| [Backend Development](development/backend.md) | Dev model, tests, migrations, layering conventions, adding routes / MCP / skills |
+| [Frontend Development](development/frontend.md) | Builds, directory conventions, API call conventions, edition gating |
+| [Chat UI Customization](development/frontend-customization.md) | Page configuration, customer component packages and standalone frontends |
-- [Browser automation plugin](modules/browser-automation.md)
+**Workbench applications**
-- [Personal Agent](modules/personal-agent.md)
+| Document | Description |
+|------|------|
+| [Workbench Protocol](development/workbench-protocol.md) | JSON Schema, protocol versions and integration |
+| [Workbench SDK and Plugin Pages](development/workbench-sdk.md) | SDK 1.0.0 and UI v3 plugin pages |
+| [Development Preview](development/workbench-preview.md) | Standalone project and permission preview |
+| [Build and Offline Artifacts](development/workbench-build.md) | Building and delivering the protocol, SDK and host frontend |
+| [Routing and Plugin Navigation](development/workbench-routing.md) | `/apps/` URLs and applicationKey |
+| [Application Resources](development/workbench-resources.md) | In-package resource runtime |
+| [UI State and Bounded Keep-alive](development/workbench-state.md) | State schema versions, save and restore |
+| [Badges, Notifications and Background Work](development/workbench-activity.md) | badge, notification and background capabilities |
+| [Platform Adapters](development/workbench-platform-adapters.md) | Web, Tauri and Electron adapter interfaces |
+| [Origins and Offline Installation](development/workbench-origins.md) | Entries for same-name local and cloud plugins |
+| [Plugin Upgrades and Permission Approval](development/workbench-plugin-upgrades.md) | Upgrades, reinstalls and inherited grants |
+| [Catalog and Layout Policies](development/workbench-access-policies.md) | Catalog API, availability and default entries |
+| [Module Navigation and App Catalog](development/workbench-navigation-preferences.md) | Sidebar modules and the app center |
+| [Catalog Notifications and Revocation](development/workbench-invalidation.md) | ETags and catalog invalidation |
+| [External Applications](development/workbench-external-apps.md) | Managing and using external link applications |
+| [External Application Authentication](development/workbench-external-auth.md) | Authentication setup for external applications (EE console) |
+| [External Page Embedding](development/workbench-external-embedding.md) | Origins and readiness for embedded pages |
+| [Application Diagnostics](development/workbench-diagnostics.md) | Diagnostic IDs and retries |
+| [Error Codes](development/workbench-errors.md) | Workbench error codes |
+| [Administrator Guide](development/workbench-administration.md) | Workbench administration |
+| [Compatibility and Migration](development/workbench-migration.md) | Version compatibility and migration |
diff --git a/document/en/api/overview.md b/document/en/api/overview.md
index d8c14af6..dff5828d 100644
--- a/document/en/api/overview.md
+++ b/document/en/api/overview.md
@@ -59,13 +59,13 @@ The backend runs several parallel authentication mechanisms, each covering a dif
| Mechanism | How to send the credential | Scope | Implementation |
|---|---|---|---|
| **Session cookie** | `Cookie: jx_session=` (set automatically on login) | All end-user-facing `/v1/*` endpoints (chats, projects, kb, memories, …) | `get_current_user`: cookie → Redis session lookup |
-| **Personal API key** | `Authorization: Bearer sk-jx-...` | Same user identity as a session cookie; intended for scripts / third-party integrations | Recognized by the `sk-jx-` prefix; plaintext returned only once at creation, DB stores SHA256 only (`core/services/api_key_service.py`); self-managed at `/v1/me/api-keys` |
+| **Personal API key** | `Authorization: Bearer sk-jx-...` | Same user identity as a session cookie; intended for scripts / third-party integrations | Recognized by the `sk-jx-` prefix; plaintext returned only once at creation, DB stores SHA256 only (`core/auth/api_keys.py`); self-managed at `/v1/me/api-keys` |
| **Bearer token (mock/remote mode)** | `Authorization: Bearer ` | `AUTH_MODE=mock` (development: any token, or none, resolves to the mock user) or `remote` (verified against the user center) | Fallback branch of `get_current_user`; not accepted under `AUTH_MODE=session` |
| **ADMIN_TOKEN** | `Authorization: Bearer ` | `/admin` console backend (`/v1/admin/skills`, `/v1/admin/kb`, …) and content-block write endpoints | `require_admin` (env var `ADMIN_TOKEN`) |
| **CONFIG_TOKEN** | `Authorization: Bearer ` | `/config` console backend (`/v1/config/*`, `/v1/models`, `/v1/service-configs`, some `/v1/admin/*` observability endpoints) | `require_config` (env var `CONFIG_TOKEN`) |
| **ADMIN or CONFIG (either)** | Same as above | Prompt snapshot export/import (`/v1/content/prompts/export|import`) | `require_admin_or_config` |
| **super_admin session** | Session cookie (user with `extra_data.role == "super_admin"`); a valid ADMIN_TOKEN works as fallback | A few cross-user management operations | `require_super_admin` |
-| **BACKEND_INTERNAL_TOKEN** | `Authorization: Bearer ` | Internal site, browser and security-tool service calls; requests are rejected when unset | `api/routes/v1/internal_site_auth.py`, `internal_security.py` |
+| **BACKEND_INTERNAL_TOKEN** | `Authorization: Bearer ` | Internal site, browser and security-tool service calls; requests are rejected when unset | `features/sites/api/internal_site_auth.py`, `internal_security.py` |
Resolution order in `get_current_user`: **cookie session → API-key Bearer (`sk-jx-` prefix) → mock/remote Bearer**. A Bearer that looks like an API key but fails validation gets an immediate 401 — it is never silently downgraded to anonymous.
@@ -120,7 +120,7 @@ curl -X POST 'https:///api/v1/agents/responses' \
## SSE Streaming Protocol
-The streaming chat endpoint `POST /v1/agents/responses` (`src/backend/api/routes/v1/agent_responses.py`, `stream: true`) starts a background run after validation, then follows that run over SSE; after a disconnect, the client can resume from any offset via `GET /v1/chats/stream/{run_id}`. Events are produced by `src/backend/orchestration/workflow.py` and serialized to the wire by `src/backend/orchestration/chat_run_executor.py`.
+The streaming chat endpoint `POST /v1/agents/responses` (`src/backend/api/routes/v1/agent_responses.py`, `stream: true`) starts a background run after validation, then follows that run over SSE; after a disconnect, the client can resume from any offset via `GET /v1/chats/stream/{run_id}`. Events are produced by `src/backend/core/agent_runtime/runner.py` and serialized to the wire by `src/backend/core/chat/runtime/`.
**Wire format**: `Content-Type: text/event-stream`; each event is one `data: {JSON}` line with the event type in the JSON `type` field (the SSE `event:` line is not used). The stream terminates with `data: [DONE]`. After 15 seconds of silence, an SSE comment line `: heartbeat` keeps reverse-proxy connections alive.
@@ -197,7 +197,7 @@ All endpoints use the current user identity and verify chat ownership. Pending r
The answer count, question IDs, and order must exactly match the request. Private option IDs must come from the pending response, a single-select answer cannot contain multiple options, and every question must be answered or explicitly use `skipped=true`. The backend projects those private IDs back to the original option labels before returning the DSH-compatible model result `{"answers":[{"id":"scope","selected":["Minimal"]}]}`. The first valid answer/cancel atomically claims the request; concurrent submissions return `stale=true` and cannot wake the tool twice. A successful POST represents that atomic server claim, so the submitting tab may remove the exact `request_id` immediately; `user_question_resolved` and periodic authoritative pending snapshots synchronize other tabs and disconnected clients.
-`[ref:tool_name-N]` markers in the answer text are parsed into `citations` items by `orchestration/citations.py`; the frontend renders them as citation badges (see [Chat module](../modules/chat.md)).
+`[ref:tool_name-N]` markers in the answer text are parsed into `citations` items by `core/llm/citations/`; the frontend renders them as citation badges (see [Chat module](../modules/chat.md)).
### Resume, Steer, and cancel
diff --git a/document/en/architecture/backend.md b/document/en/architecture/backend.md
index 09e4841b..18029b3a 100644
--- a/document/en/architecture/backend.md
+++ b/document/en/architecture/backend.md
@@ -128,7 +128,7 @@ The root contains only the protocol, errors, factory, and package initializer. I
The runner depends only on process and path primitives that do not load backend settings or databases. OpenSandbox-specific components stay with that provider; shared processes, paths, and account environment support serve both OpenSandbox and Cube.
-### core/agent_skills — the skill engine
+### core/capabilities/skills — the skill engine
| File | Responsibility |
|---|---|
@@ -144,12 +144,12 @@ The runner depends only on process and path primitives that do not load backend
| Submodule | Responsibilities and key files |
|---|---|
| `core/chat` | Workflow context assembly (`context.py`), SSE tool-log event construction (`tool_log.py`) |
-| `core/content` | Attachment parsing (`file_parser.py`), KB document chunking/keywords/vectorization (`kb_processing.py`), upload validation (`file_validation.py`), artifact reading and summaries (`artifact_reader/refs/summary.py`), content-block import/export (`content_blocks.py`), `svg_fit.py` |
-| `core/kb` / `edition_ee/kb` | Shared private-KB parsing and Milvus vector storage stay in `core/kb`; the Dify client and external retrieval adapter live only in `edition_ee/kb` (EE) |
-| `core/artifacts` | Artifact store `store.py`: local / OSS dual mode |
-| `core/infra` | Unified responses (`responses.py`), exceptions (`exceptions.py`), structured logging (`logging.py`), rate limiting (`rate_limit.py`), Redis singleton (`redis.py`), metrics (`metrics.py`), background-task registry (`runtime_state.py`), data masking (`data_masking.py`), distillation budget gates (`distillation_budget.py`, Enterprise Edition, EE) |
+| `core/files/content` | Attachment parsing (`file_parser.py`), KB document chunking/keywords/vectorization (`kb_processing.py`), upload validation (`file_validation.py`), artifact reading and summaries (`artifact_reader/refs/summary.py`), content-block import/export (`content_blocks.py`), `svg_fit.py` |
+| `features/knowledge` / `edition_ee/kb` | Shared private-KB parsing and Milvus vector storage stay in `features/knowledge`; the Dify client and external retrieval adapter live only in `edition_ee/kb` (EE) |
+| `core/files/artifacts` | Artifact store `store.py`: local / OSS dual mode |
+| `core/infra` | Unified responses (`responses.py`), exceptions (`exceptions.py`), structured logging (`logging.py`), Redis singleton (`redis.py`), metrics (`metrics.py`), background-task registry (`runtime_state.py`), data masking (`data_masking.py`), distillation budget gates (`distillation_budget.py`, Enterprise Edition, EE) |
| `edition_ee/licensing` | Feature enums, signature verification, clock guard, seat policy, middleware, and the manager all exist only in EE; the CE tree has no License package and gets a fixed edition probe from its `api/middleware/edition.py` overlay |
-| `core/storage` | Storage protocol `protocol.py` + factory `factory.py`; `local.py` (CE), `s3.py` / `oss.py` (Enterprise Edition, EE) |
+| `core/files/storage` | Storage protocol `protocol.py` + factory `factory.py`; `local.py` (CE), `s3.py` / `oss.py` (Enterprise Edition, EE) |
## orchestration/ — the orchestration layer
@@ -219,12 +219,12 @@ CE routes live under `api/routes/v1/`; physically split commercial routes live u
| App entry and startup hooks | `src/backend/api/app.py` |
| Router registry | `src/backend/api/routes/v1/__init__.py` |
| Agent factory | `src/backend/core/llm/factory/build.py` |
-| Run executor / workflow | `src/backend/orchestration/chat_run_executor.py`, `workflow.py` |
-| Capability catalog | `src/backend/core/config/catalog.py` |
+| Run executor / workflow | `src/backend/core/chat/runtime/`, `workflow.py` |
+| Capability catalog | `src/backend/core/capabilities/catalog/` |
| Sandbox protocol | `src/backend/core/sandbox/protocol.py` |
| Memory pipeline | `src/backend/core/memory/pipeline.py` |
| EE License implementation / CE edition middleware | `src/backend/edition_ee/licensing/`, `src/backend/api/middleware/edition.py` |
-| Skill engine | `src/backend/core/agent_skills/loader.py` |
+| Skill engine | `src/backend/core/capabilities/skills/loader.py` |
| MCP registry and ports | `src/backend/extensions/mcp_servers/registry.py`, `_ports.py` |
The space MCP interface lives in `extensions/mcp_servers/space_files_mcp/`. The registry separates installation placement from exposure. Hybrid desktops access spaces through the capability gateway; mutations use unified approval. See [space file mounts](../deployment/space-files.md).
diff --git a/document/en/architecture/data-model.md b/document/en/architecture/data-model.md
index 0c1a94c2..95f89d71 100644
--- a/document/en/architecture/data-model.md
+++ b/document/en/architecture/data-model.md
@@ -179,11 +179,11 @@ A few tables that *look* EE but are required by CE are deliberately excluded fro
|---|---|
| Shared ORM / EE ORM | `src/backend/core/db/models/`, `src/backend/edition_ee/db/models/` |
| Ontology repository | `src/backend/core/db/repository/ontology.py` |
-| Feedback model / repository | `src/backend/edition_ee/db/models/feedback.py`, `db/repository/feedback.py` |
+| Feedback model / repository | `src/backend/edition_ee/db/models/feedback.py`, `edition_ee/db/repository/feedback.py` |
| Engine and startup table creation | `src/backend/core/db/engine.py` |
| Repository layer | `src/backend/core/db/repository/` |
-| CE/EE table boundary | `src/backend/core/db/edition_tables.py` |
+| CE/EE table boundary | `src/backend/edition_ee/db/edition_tables.py` |
| EE migration chain | `src/backend/alembic/versions/` |
| CE migration baseline | `ce/overlay/src/backend/alembic/versions/ce_0001_initial.py` |
-| Prompt version pool service | `src/backend/core/services/prompt_version_service.py` |
+| Prompt version pool service | `src/backend/core/llm/prompt_versions.py` |
| Authoritative edition boundary | [Editions](../editions/overview.md) |
diff --git a/document/en/architecture/overview.md b/document/en/architecture/overview.md
index 9105c485..3fc350d6 100644
--- a/document/en/architecture/overview.md
+++ b/document/en/architecture/overview.md
@@ -1,8 +1,8 @@
# Architecture Overview
-> Last updated: 2026-07-19
+> Last updated: 2026-10-23
-HugAgentOS is an enterprise-grade AgentOS with both a lightweight local profile and a fully containerized production profile. A React single-page application provides the user experience, an async FastAPI service runs the backend, AgentScope 2.0's ReActAgent supplies the agent runtime, independent MCP (Model Context Protocol) processes expose tools, and pluggable sandbox providers isolate code execution. The platform ships in two editions — an open-source Community Edition (CE) and a commercial Enterprise Edition (EE) — built from a single codebase and separated by three runtime seams: the router registry, the table-creation boundary, and license feature gates (see [Editions](../editions/overview.md)).
+HugAgentOS is an open-source, self-hosted agent harness that runs as HugAgentOS Cloud (a fully containerized server) or HugAgentOS Local (on the desktop). A React single-page application provides the user experience, an async FastAPI service runs the backend, AgentScope 2.0's ReActAgent supplies the agent runtime, independent MCP (Model Context Protocol) processes expose tools, and pluggable sandbox providers isolate code execution. The platform ships in two editions — an open-source Community Edition (CE) and a commercial Enterprise Edition (EE) — built from a single codebase and separated by three runtime seams: the router registry, the table-creation boundary, and license feature gates (see [Editions](../editions/overview.md)).
This page describes the system from five angles: the layered architecture, the ontology-grounded enterprise trust plane, the full lifecycle of one chat request, the container topology, and the key design decisions. For deeper dives, see [Backend Architecture](./backend.md), [Frontend Architecture](./frontend.md), and the [Data Model](./data-model.md).
@@ -23,51 +23,43 @@ This page describes the system from five angles: the layered architecture, the o
```
┌────────────────────────────────────────────────────────────────────┐
-│ Browser (React SPA) │
-│ App.tsx main app · AdminApp content console (EE) │
-│ ConfigApp system console (EE) │
-│ api.ts typed client + useStreaming SSE parser │
+│ Entry: web (React SPA) · desktop (Tauri) · IM channels · API · │
+│ schedules · heartbeats │
└──────────────────────────────┬─────────────────────────────────────┘
- │ HTTP / SSE (under /api)
+ │ HTTP / SSE (/api prefix)
┌──────────────────────────────▼─────────────────────────────────────┐
-│ Nginx (frontend container, :80) │
-│ Static assets · /api → backend reverse proxy │
+│ FastAPI (src/backend/api/) │
+│ Middleware: CORS · structured logging · error handling · │
+│ edition router registry │
└──────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────────┐
-│ FastAPI (backend container, src/backend/api/) │
-│ Middleware: CORS · structured logging · unified error handling │
-│ Router registry: CE_ROUTERS + EE_ROUTERS (license feature gates) │
+│ Run layer │
+│ core/runs: persisted runs, database leases, startup reconciliation,│
+│ tool effect intents and receipts │
+│ core/chat/runtime: run admission, chat worker, resume, steering, │
+│ compaction │
└──────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────────┐
-│ Orchestration layer (src/backend/orchestration/) │
-│ chat_run_executor: decouples Runs from HTTP, resumable streams │
-│ workflow: streaming orchestration · strategy: routing │
-│ citations: reference extraction · memory_integration │
-│ subagents/plan_mode · schedulers: automation + distillation (EE) │
-└──────────────────────────────┬─────────────────────────────────────┘
- │
-┌──────────────────────────────▼─────────────────────────────────────┐
-│ Agent layer (src/backend/core/llm/) │
-│ factory/build → AgentScope 2.0 ReActAgent │
-│ ToolCollector → Toolkit (built-in tools + skills + MCP tools) │
-│ middlewares: dynamic model / file context / system reminders │
-│ offloader: oversized tool results spilled to sandbox │
-│ prompts/prompt_runtime: system prompt assembly (DB-first) │
+│ Agent execution │
+│ core/agent_runtime: runner.astream_chat_workflow, routing, │
+│ memory / ontology / evolution integration │
+│ core/llm: factory/build → AgentScope ReActAgent, tools, context │
+│ orchestration/: plans · loops (autonomous loop) · automations │
+│ personal_agent/: identity, task queue, scheduling, channels, │
+│ device links, workspace │
└───────┬──────────────────────┬──────────────────────┬──────────────┘
- │ streamable-http │ sandbox protocol │ SQL / vectors
+ │ streamable-http │ sandbox provider │ SQL / vector
┌───────▼───────────┐ ┌────────▼──────────┐ ┌─────────▼─────────────┐
-│ MCP tool pool │ │ Sandboxes │ │ Data layer │
-│ mcp container, │ │ script-runner(CE) │ │ PostgreSQL · Redis │
-│ tool processes │ │ OpenSandbox CE/EE │ │ Milvus · Neo4j (opt.) │
-│ see _ports.py │ │ CubeSandbox (EE) │ │ Local / S3 / OSS │
-│ search/fetch/tools │ │ │ │ storage │
-│ plugins/dwh/chain(EE)│ │ │ │ │
+│ MCP tool pool │ │ Sandbox and jobs │ │ Data layer │
+│ mcp processes │ │ OpenSandbox │ │ PostgreSQL / SQLite │
+│ ports: _ports.py │ │ script-runner │ │ Redis · Milvus · Neo4j│
+│ plugin tools │ │ core/jobs │ │ S3-compatible / local │
└───────────────────┘ └───────────────────┘ └───────────────────────┘
```
-Dependencies point strictly downward: `api → orchestration → core`, and within `core`, `services → db`. MCP servers are independent processes that talk to the backend exclusively over HTTP, so a crash on either side never takes down the other.
+Dependencies point strictly downward: `api → features / orchestration / personal_agent → core`; `core` never depends on upper layers, and `integrations/` holds connectors to external systems such as DingTalk, Feishu and email. MCP servers are independent processes that talk to the backend exclusively over HTTP, so a crash on either side never takes down the other.
## Ontology-Grounded Enterprise Trust Plane
@@ -150,21 +142,22 @@ The entry point is `POST /v1/agents/responses` (`src/backend/api/routes/v1/chats
### 2. Starting a Run (decoupled from HTTP)
-`orchestration/chat_run_executor.py::start_run` registers the AI workflow as a background Run (persisted to the `chat_runs` table); the HTTP response merely *follows* that Run:
+`core/chat/runtime/admission.py::start_run` registers the turn as a background Run (persisted to the `chat_runs` table), executed by a chat worker that holds a database lease; the HTTP response merely *follows* that Run:
-- After a dropped connection or page refresh, the client calls `GET /v1/chats/stream/{run_id}` to resume from a given offset;
-- On backend restart, the lifespan hook `_startup_recover_chat_runs` recovers or reaps unfinished Runs.
+- After a dropped connection or page refresh, the client calls `GET /v1/chats/stream/{run_id}?from=N` to resume from a given offset;
+- While running, `POST /v1/chat-runs/{run_id}/steer` adds an instruction that applies at the next safe ReAct boundary;
+- On startup, `core/chat/runtime/recovery.py::recover_orphan_runs` reconciles Runs whose lease has expired from their last committed safe point: recoverable ones continue, unrecoverable ones are closed explicitly.
### 3. Orchestration and agent execution
-`orchestration/workflow.py::astream_chat_workflow` drives a single turn:
+`core/agent_runtime/runner.py::astream_chat_workflow` drives a single turn:
-1. **Memory retrieval** — `orchestration/memory_integration.py` reads the L1 profile / L2 vector memories (when enabled) and injects them as a system message;
-2. **Routing decision** — `orchestration/strategy.py`; `ROUTER_STRATEGY=main_only` (default) always routes to the main agent;
+1. **Memory retrieval** — `core/agent_runtime/integrations/memory.py` reads the L1 profile / L2 vector memories (when enabled) and injects them as a system message;
+2. **Routing decision** — `core/agent_runtime/strategy.py`; `ROUTER_STRATEGY=main_only` (default) always routes to the main agent;
3. **Agent construction** — `core/llm/factory/build.py::create_agent_executor`: assembles the prompt (`prompts/prompt_runtime.py`, DB version pool first), filters MCP servers by catalog and per-user capabilities, registers built-in tools (Read/Write/Edit/Glob/Grep/bash/skill loading, see `core/llm/tools/`), then constructs the AgentScope Toolkit in one shot;
-4. **Streaming execution** — `orchestration/streaming.py::StreamingAgent` wraps the ReActAgent and yields reasoning deltas, tool calls, and tool results as they happen;
-5. **Citation extraction** — `orchestration/citations.py` parses `[ref:tool_name-N]` markers out of tool results into citation items;
-6. **Wrap-up** — the assistant message and tool-call logs are persisted, `core/memory/pipeline.py` extracts memories asynchronously in the background (off the SSE hot path), and follow-up questions are generated by a separate endpoint (`orchestration/followups.py`).
+4. **Streaming execution** — `core/llm/runtime/streaming.py::StreamingAgent` wraps the ReActAgent and yields reasoning deltas, tool calls, and tool results as they happen;
+5. **Citation extraction** — `core/llm/citations/` parses `[ref:tool_name-N]` markers out of tool results into citation items;
+6. **Wrap-up** — the assistant message and tool-call logs are persisted, `core/memory/pipeline.py` extracts memories asynchronously in the background (off the SSE hot path), and follow-up questions are generated by a separate endpoint (`core/chat/followups.py`).
### 4. The SSE event stream
@@ -177,9 +170,9 @@ Each event is `data: {json}\n\n` with a `type` discriminator; the stream termina
| `tool_call_start` | Tool invocation starts (stable ID + name) | `core/chat/tool_log.py` |
| `tool_call_delta` | Batched incremental tool-argument JSON | `core/chat/tool_log.py` |
| `tool_call` | Tool arguments are complete and execution is about to start | `core/chat/tool_log.py` |
-| `tool_result` | Tool execution result (with citations, artifact card payloads) | `core/chat/tool_log.py` + `orchestration/tool_payloads.py` |
+| `tool_result` | Tool execution result (with citations, artifact card payloads) | `core/chat/tool_log.py` + `core/chat/tool_payloads.py` |
| `tool_progress` | Progress reports from long-running tools | `chats.py` |
-| `meta` | Trailing metadata: route, citation sources, artifact list, etc. | `orchestration/workflow.py` |
+| `meta` | Trailing metadata: route, citation sources, artifact list, etc. | `core/agent_runtime/runner.py` |
| `error` | Error event (immediately followed by `[DONE]`) | `chats.py` |
The frontend's `src/frontend/src/hooks/useStreaming.ts` dispatches on `type` and merges the start, deltas, completed call, and result in place into one tool card keyed by `tool_id`.
@@ -192,13 +185,14 @@ The frontend's `src/frontend/src/hooks/useStreaming.ts` dispatches on `type` and
|---|---|---|---|
| `frontend` | hugagent-frontend | `3002:80` | Nginx: static assets + `/api` reverse proxy |
| `backend` | hugagent-backend | `3001` | FastAPI main service |
-| `mcp` | hugagent-mcp | dedicated internal ports | 9 MCP servers as long-running streamable-http processes (7 general CE tools plus 2 EE industry tools) |
+| `mcp` | hugagent-mcp | dedicated internal ports | Built-in MCP servers as long-running streamable-http processes (data warehouse and industry tools are EE) |
| `postgres` | hugagent-postgres | `5432` | Primary database |
| `redis` | hugagent-redis | `6379` | Sessions / caching |
-| `script-runner` (profile `script_runner`) | hugagent-script-runner | internal | Lightweight script-execution sidecar (no DB/secret access) |
-| `opensandbox` + `opensandbox-config-init` (profile `opensandbox`) | hugagent-opensandbox | internal | Persistent sandbox (Enterprise Edition, EE) |
+| `script-runner` (profile `script_runner`) | hugagent-script-runner | internal | Optional lightweight script-execution sidecar (no DB/secret access) |
+| `opensandbox` + `opensandbox-config-init` (profile `opensandbox`) | hugagent-opensandbox | internal | Default sandbox: persistent sessions and snapshot recovery (mutually exclusive with script-runner) |
| `etcd` / `minio` / `milvus` (profile `mem0`) | hugagent-etcd etc. | internal | L2 vector-memory infrastructure |
| `neo4j` (profile `mem0`) | hugagent-neo4j | `7474/7687` | L3 graph memory (optional) |
+| `personal-agent-opensandbox` (profile `personal-agent`, `docker/personal-agent/compose.personal-agent.yml`) | — | internal | Dedicated controller for Personal Agent permanent computers |
All services share the `hugagent-network` network; named volumes such as `postgres_data` persist across rebuilds. See [Docker Compose deployment](../deployment/docker-compose.md) for details.
@@ -206,7 +200,7 @@ All services share the `hugagent-network` network; named volumes such as `postgr
### Catalog as the single source of truth
-`src/backend/core/config/catalog.json` is the sole authority for capability switches, with four kinds of entries: `skills` / `agents` / `mcp` / `kb`. `core/config/catalog.py` exposes `is_enabled(kind, id)` / `get_enabled_ids(kind)`, gating everything from MCP tool loading and skill registration to routing decisions; admin toggles are stored as overrides in the `catalog_overrides` table layered on top of the file (`core/config/catalog_loader.py`). Adding a capability to the catalog makes it visible to both the frontend capability center and the agent factory — no orchestration changes required. See [Capability Catalog](../modules/catalog.md).
+`src/backend/core/config/catalog.json` is the sole authority for capability switches, with four kinds of entries: `skills` / `agents` / `mcp` / `kb`. `core/capabilities/catalog/` exposes `is_enabled(kind, id)` / `get_enabled_ids(kind)`, gating everything from MCP tool loading and skill registration to routing decisions; admin toggles are stored as overrides in the `catalog_overrides` table layered on top of the file (`core/capabilities/catalog/loader.py`). Adding a capability to the catalog makes it visible to both the frontend capability center and the agent factory — no orchestration changes required. See [Capability Catalog](../modules/catalog.md).
### Unified response envelope
@@ -214,19 +208,19 @@ Every v1 endpoint returns `{ code, message, data, trace_id, timestamp }`, produc
### Prompts live in the database
-System prompts are no longer hardcoded: the version pool is stored in the `content_blocks` table (`id=prompt_versions`), managed by `core/services/prompt_version_service.py` (CRUD, activation, first-boot seeding). At assembly time, `prompts/prompt_runtime.py` degrades through three levels — active DB version → file fallback under `prompts/prompt_text/default/system/` → a minimal hardcoded fallback — guaranteeing a non-empty prompt. Operators can canary-switch prompt versions from the admin console without a release. See [Prompt System](../modules/prompts.md).
+System prompts are no longer hardcoded: the version pool is stored in the `content_blocks` table (`id=prompt_versions`), managed by `core/llm/prompt_versions.py` (CRUD, activation, first-boot seeding). At assembly time, `prompts/prompt_runtime.py` degrades through three levels — active DB version → file fallback under `prompts/prompt_text/default/system/` → a minimal hardcoded fallback — guaranteeing a non-empty prompt. Operators can canary-switch prompt versions from the admin console without a release. See [Prompt System](../modules/prompts.md).
### MCP tools as independent processes
-Each tool is a standalone MCP server under `src/backend/extensions/mcp_servers//`, all running inside the dedicated `mcp` container as long-lived streamable-http listeners (the port table in `extensions/mcp_servers/_ports.py` is the single source of truth). The backend connects through the HTTP client pool in `core/llm/mcp_manager.py`. Benefits: a tool crash never affects the main process, document editing and export have moved into sandbox skills, and the tool tier can scale independently. CE keeps 7 general-purpose servers; the two industry tools, `query_database` and `ai_chain_information_mcp`, depend on intranet data sources and belong to the Enterprise Edition (EE). See [MCP Tools](../modules/mcp-tools.md).
+Each tool is a standalone MCP server under `src/backend/extensions/mcp_servers//`, all running inside the dedicated `mcp` container as long-lived streamable-http listeners (the port table in `extensions/mcp_servers/_ports.py` is the single source of truth). The backend connects through the run-owned native client in `core/llm/mcp/lifetime.py`. Benefits: a tool crash never affects the main process, document editing and export have moved into sandbox skills, and the tool tier can scale independently. The two industry tools, `query_database` and `ai_chain_information_mcp`, depend on intranet data sources and belong to the Enterprise Edition (EE). See [MCP Tools](../modules/mcp-tools.md).
-### Runs decoupled from connections
+### Runs decoupled from connections and processes
-Streaming chats are not tied to the HTTP connection lifecycle: `chat_run_executor` persists execution state to the `chat_runs` table and supports offset-based resumption — eliminating "refresh loses the reply" and flaky-network drops — and the same execution channel is reused by the automation scheduler.
+Streaming chats are tied neither to the HTTP connection nor to a particular process: run state and per-step context are persisted, workers claim execution through database leases, and a stale executor can no longer commit results or call tools; when a process dies, another worker takes over. Tool calls record an intent and complete only with a receipt (`core/runs/tool_effects/`); recovery replays only calls proven not to have taken effect, and external operations with an unknown outcome go to the user for confirmation. Automation, Plan Mode, the autonomous loop and Personal Agent reuse the same run machinery.
### One codebase, two editions
-The commercial main repository carries the full code; the Community Edition is derived by `scripts/build_ce.py` according to `ce/manifest.yaml` (exclude `edition_ee` implementations + text transforms + CE overlays). Runtime boundaries include the router registry `api/routes/v1/__init__.py`, the table-creation boundary `core/db/edition_tables.py` (CE never registers the 20 EE-only tables), EE's `edition_ee/licensing/` package versus CE's fixed edition middleware, and policy seams such as site visibility. See [Editions](../editions/overview.md).
+The commercial main repository carries the full code; the Community Edition is derived by `scripts/build_ce.py` according to `ce/manifest.yaml` (exclude `edition_ee` implementations + text transforms + CE overlays). Runtime boundaries include the router registry `api/routes/v1/__init__.py`, the table-creation boundary `edition_ee/db/edition_tables.py` (CE never registers the 20 EE-only tables), EE's `edition_ee/licensing/` package versus CE's fixed edition middleware, and policy seams such as site visibility. See [Editions](../editions/overview.md).
## Related Source
@@ -234,12 +228,13 @@ The commercial main repository carries the full code; the Community Edition is d
|---|---|
| FastAPI app and startup hooks | `src/backend/api/app.py` |
| Streaming chat entry point | `src/backend/api/routes/v1/chats/__init__.py` |
-| Run executor | `src/backend/orchestration/chat_run_executor.py` |
-| Streaming orchestration | `src/backend/orchestration/workflow.py` |
+| Run executor | `src/backend/core/chat/runtime/` |
+| Streaming orchestration | `src/backend/core/agent_runtime/runner.py` |
| Agent factory | `src/backend/core/llm/factory/build.py` |
| MCP config and ports | `src/backend/core/config/mcp_config.py`, `src/backend/extensions/mcp_servers/_ports.py` |
-| Capability catalog | `src/backend/core/config/catalog.json`, `catalog.py` |
+| Capability catalog | `src/backend/core/config/catalog.json`, `src/backend/core/capabilities/catalog/` |
+| Runs, leases and recovery | `src/backend/core/runs/`, `src/backend/core/chat/runtime/recovery.py` |
| Response envelope | `src/backend/core/infra/responses.py` |
| Prompt assembly | `src/backend/prompts/prompt_runtime.py` |
-| CE/EE seams | `src/backend/api/routes/v1/__init__.py`, `src/backend/core/db/edition_tables.py`, `src/backend/api/middleware/edition.py`, `src/backend/edition_ee/licensing/` |
+| CE/EE seams | `src/backend/api/routes/v1/__init__.py`, `src/backend/edition_ee/db/edition_tables.py`, `src/backend/api/middleware/edition.py`, `src/backend/edition_ee/licensing/` |
| Container orchestration | `docker-compose.yml` |
diff --git a/document/en/deployment/README.md b/document/en/deployment/README.md
index 85827d04..f8fdfab2 100644
--- a/document/en/deployment/README.md
+++ b/document/en/deployment/README.md
@@ -9,17 +9,14 @@ HugAgentOS supports several deployment methods, from "zero-dependency single-mac
| Method | Best for | Docker | Database | Multi-user | Doc |
|---|---|---|---|---|---|
| **Windows desktop one-click install** | Personal Windows use; install the local service with the desktop client | Not required | SQLite | No (single user) | [windows-deployment.md](windows-deployment.md) |
-| **UOS 1070 ARM64 desktop** | UOS 1070 aarch64; Electron 43 `.deb` | Not required | SQLite / remote | Local single user / cloud multi-user | [uos1070-desktop.md](uos1070-desktop.md) |
| **No-Docker quick install** | Personal single-machine trial, development experience; one command and you're running | Not required | SQLite | No (single user) | [quick-install.md](quick-install.md) |
| **Docker Compose** | The standard form for teams / production — multi-user, full features | Required | PostgreSQL | Yes | [docker-compose.md](docker-compose.md) |
-| **Offline production (Enterprise Edition)** | Air-gapped environments (government intranets); image tarball offline delivery | Required | PostgreSQL | Yes | [offline-production.md](offline-production.md) |
Cross-platform and reference:
| Doc | Description |
|---|---|
| [Windows Deployment](windows-deployment.md) | Installing the desktop-managed local service, or running Compose through Docker Desktop and WSL2 |
-| [UOS 1070 ARM64 Desktop](uos1070-desktop.md) | Electron 43, glibc 2.28, ARM64 `.deb` build and acceptance |
| [Environment Variables](environment-variables.md) | Complete variable reference (defaults / purpose / CE·EE relevance) |
## One-line comparison
@@ -31,7 +28,6 @@ Cross-platform and reference:
single-user deployment.
- **No-Docker quick install**: the fastest command-line path on Linux and macOS. Run `curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash`; the installer creates the admin account, configures the model, starts the server, and opens the browser. Data lives under `~/.hugagent/`. **Single process, single user** — ideal for personal trials and development, not for multi-user or production.
- **Docker Compose**: the recommended standard deployment. All services are orchestrated by one `docker-compose.yml` (PostgreSQL + Redis + backend + MCP + frontend + sandbox), supporting multi-user, persistent sandboxes, layered memory, and every other capability.
-- **Offline production (EE)**: for isolated environments that cannot pull images online — build image tarballs on a connected machine, copy them to production, `docker load` + `compose up`. Part of the Enterprise Edition delivery scope.
> For capability differences between the Community and Enterprise editions, see [Edition Comparison](../editions/overview.md).
diff --git a/document/en/deployment/artifact-storage-key-repair.md b/document/en/deployment/artifact-storage-key-repair.md
deleted file mode 100644
index 7370f91f..00000000
--- a/document/en/deployment/artifact-storage-key-repair.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Repair historical artifact storage keys
-
-Older releases could register `artifacts/.docx` objects using the placeholder
-`artifacts/`. Generated-file persistence now requires a real storage key.
-Personal-space metadata reads drain local writes without requiring every remote
-file to download. Import failures show an error and retry control instead of an empty list.
-
-## Safety
-
-Repair matches only `storage_key == artifacts/`. It requires confirmed
-absence of that original object, an existing object with the recorded filename suffix,
-and downloaded bytes matching the recorded size. Timeouts, authorization errors,
-missing replacements and size mismatches leave the record unchanged. Existing
-extensionless objects are preserved.
-
-Updates compare the original key, filename, size and content timestamp atomically.
-They preserve the content version. The old/new keys, SHA-256 and original content
-version are committed together in `metadata.storage_key_repair` for audit and
-conditional rollback. Failed personal-space projection uses the same verified repair;
-existing conflict protection preserves unsaved local edits.
-
-## Operation
-
-Follow repository release approval rules. Validate in staging first and obtain
-separate approval before production code or data changes. Run from the project root
-using the authorized environment runtime and configuration. Never copy credentials
-or `.env` files between environments.
-
-```bash
-# Default: verify and print candidates without changing database records.
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id
-
-# Apply after reviewing candidates; already repaired records are skipped.
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id --apply
-
-# Undo audited repairs only while the current key and content version are unchanged.
-# This restores the old key. An upgraded background projector may repair it again.
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id --rollback
-```
-
-The tool processes one explicitly selected account in ID pages and exits nonzero on
-errors. It includes historical deleted records without restoring files or changing
-folder ownership.
-
-## Verification
-
-1. Confirm candidate keys, object sizes and audit records; repeat runs make no duplicate changes.
-2. Verify successful root/folder listing APIs and downloads of valid files.
-3. In Windows desktop local mode, select a file using both `@` and `+ → Import from My Space`, then send it and verify the document content is readable.
-4. With one unavailable object, other metadata remains accessible and fetching the affected file reports failure.
-5. Failed requests expose retry; old responses cannot overwrite a newer filter or folder.
-
-Frontend changes require a desktop client release to affect installed clients.
-A successful source build does not imply publication or production data changes.
-Record staging, production and desktop verification separately.
diff --git a/document/en/deployment/desktop-local-mode.md b/document/en/deployment/desktop-local-mode.md
index 12dfe662..b56d5d1d 100644
--- a/document/en/deployment/desktop-local-mode.md
+++ b/document/en/deployment/desktop-local-mode.md
@@ -3,7 +3,7 @@
The complete desktop package includes the platform-specific CE backend and a private Python runtime.
Starting local mode verifies and extracts the bundled resources without requiring Docker, system Python,
pip or a first-run dependency download. Thin packages support remote connections only.
-See [Windows deployment](windows-deployment.md), [UOS deployment](uos1070-desktop.md) and
+See [Windows deployment](windows-deployment.md) and
the [deployment overview](README.md) for installation options.
## Persistent data
diff --git a/document/en/deployment/docker-compose.md b/document/en/deployment/docker-compose.md
index 1dac3fd7..dc75240d 100644
--- a/document/en/deployment/docker-compose.md
+++ b/document/en/deployment/docker-compose.md
@@ -14,7 +14,7 @@ For a new deployment:
```bash
python3 scripts/configure-ce-docker.py
-docker build -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+docker build -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
docker compose build backend
docker compose up -d --build
```
@@ -68,8 +68,8 @@ Enterprise Edition (EE).
| Service | Profile | Container | Image / build | Role |
|---|---|---|---|---|
-| `script-runner` | `script_runner` | hugagent-script-runner | `docker/Dockerfile.script-runner` | Lightweight sandbox: 1 GB RAM / 1 CPU / read-only rootfs + tmpfs; skill directory mounted read-only at `/workspace/skills` |
-| `opensandbox-config-init` | `opensandbox` | hugagent-opensandbox-config-init | `python:3.12-alpine` | One-shot init: renders `@@HOST_REPO_PATH@@` / `@@HOST_STORAGE_PATH@@` in `docker/opensandbox-config.toml.tpl` into the named volume `opensandbox_config` |
+| `script-runner` | `script_runner` | hugagent-script-runner | `docker/sandbox/Dockerfile.script-runner` | Lightweight sandbox: 1 GB RAM / 1 CPU / read-only rootfs + tmpfs; skill directory mounted read-only at `/workspace/skills` |
+| `opensandbox-config-init` | `opensandbox` | hugagent-opensandbox-config-init | `python:3.12-alpine` | One-shot init: renders `@@HOST_REPO_PATH@@` / `@@HOST_STORAGE_PATH@@` in `docker/opensandbox-server/opensandbox-config.toml.tpl` into the named volume `opensandbox_config` |
| `opensandbox` | `opensandbox` | hugagent-opensandbox | `hugagent-opensandbox-server:v0.1.13-3` | Shared CE / EE controller: manages containers through Docker socket; execd runs commands and session files persist. CE publishes no host port; the full repository has a configurable debug port |
### mem0 memory infrastructure (profile `mem0`, optional)
@@ -219,19 +219,19 @@ make migrate-new msg="describe change"
| Feature | Files |
|---|---|
-| Service orchestration | `docker-compose.yml` (CubeSandbox overlay: `docker-compose.cube.yml`) |
+| Service orchestration | `docker-compose.yml` (CubeSandbox overlay: `docker/sandbox/compose.cube.yml`) |
| Backend image | `docker/Dockerfile` (multi-stage, target `production`) |
| MCP image | `docker/Dockerfile.mcp` |
-| Sandbox images | `docker/Dockerfile.script-runner`, `docker/Dockerfile.opensandbox` |
+| Sandbox images | `docker/sandbox/Dockerfile.script-runner`, `docker/sandbox/Dockerfile.opensandbox` |
| Frontend image + nginx proxy | `src/frontend/Dockerfile`, `src/frontend/nginx.conf`, `src/frontend/default.conf.template` |
| Backend entrypoint (auto migration) | `src/backend/scripts/backend_entrypoint.sh` |
-| OpenSandbox config template | `docker/opensandbox-config.toml.tpl` |
+| OpenSandbox config template | `docker/opensandbox-server/opensandbox-config.toml.tpl` |
| Migrations | `alembic.ini`, `src/backend/alembic/` |
| MCP port single source of truth | `src/backend/extensions/mcp_servers/_ports.py` |
### OpenSandbox command latency and controller image
-The controller is built from `docker/Dockerfile.opensandbox-server` as
+The controller is built from `docker/opensandbox-server/Dockerfile.opensandbox-server` as
`hugagent-opensandbox-server:v0.1.13-3`. Based on a pinned upstream release,
it inspects known sandboxes by name and verifies ownership labels. Synchronous
Docker lookups in lifecycle and proxy routes run in a thread pool so other requests
@@ -270,7 +270,7 @@ Create a dedicated BuildKit builder from the WSL project directory:
BUILDX_BUILDER=hugagent-limited COMPOSE_PARALLEL_LIMIT=1 docker compose build mcp
BUILDX_BUILDER=hugagent-limited COMPOSE_PARALLEL_LIMIT=1 docker compose build frontend
docker buildx build --builder hugagent-limited --load \
- -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+ -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
docker buildx stop hugagent-limited
Reuse the builder if it already exists. Stop it after builds; its cache remains available.
diff --git a/document/en/deployment/environment-variables.md b/document/en/deployment/environment-variables.md
index 27a293aa..e9143d4c 100644
--- a/document/en/deployment/environment-variables.md
+++ b/document/en/deployment/environment-variables.md
@@ -23,7 +23,7 @@ This reference is sourced from `.env.example` and `src/backend/core/config/setti
| `HOST_REPO_PATH` | repo absolute path | Repository root on the host; used when the backend resolves compose-relative paths through docker.sock to rebuild sandbox images | CE |
| `DOCKER_GID` | `999` | Host docker group GID granting the backend container access to `/var/run/docker.sock` (find with `stat -c '%g' /var/run/docker.sock`) | CE |
| `COMPOSE_PROFILES` | CE Docker: `opensandbox` | Compose profile selection (`script_runner` / `opensandbox`, append `,mem0` as needed); must match `SANDBOX_PROVIDER` | CE |
-| `COMPOSE_FILE` | (unset) | Compose overlay files; set `docker-compose.yml:docker-compose.cube.yml` for CubeSandbox | CE |
+| `COMPOSE_FILE` | (unset) | Compose overlay files; set `docker-compose.yml:docker/sandbox/compose.cube.yml` for CubeSandbox | CE |
| `MAX_REQUEST_SIZE` | `52428800` (code default, 50 MB) | Maximum request body size (bytes) | CE |
| `DB_MIGRATION_RETRIES` / `DB_MIGRATION_RETRY_INTERVAL` | `20` / `2` (entrypoint defaults) | Startup migration retry count / interval (seconds) | CE |
@@ -57,7 +57,7 @@ This reference is sourced from `.env.example` and `src/backend/core/config/setti
| `ENABLE_SUMMARY` | `true` | When off, titles fall back to message truncation | CE |
| `SUMMARY_MAX_ROUNDS` | `3` | Titles stop updating after this many rounds | CE |
| `OPENAI_API_KEY` / `OPENAI_API_BASE` | (empty) / `https://api.openai.com/v1` | Alternate direct OpenAI configuration (passed through by compose) | CE |
-| `ROUTER_STRATEGY` | `main_only` (code default) | Routing strategy (`orchestration/strategy.py`) | CE |
+| `ROUTER_STRATEGY` | `main_only` (code default) | Routing strategy (`core/agent_runtime/strategy.py`) | CE |
| `FOLLOWUP_ENABLED` | `true` (code default) | Follow-up suggestion generation | CE |
## Authentication and login
@@ -184,10 +184,11 @@ the database has no value.
| `MCP_HOST` | `mcp` (compose default) | MCP container hostname; set `127.0.0.1` for local debugging | CE |
| `MCP_MARKET_REVALIDATE_INTERVAL` | `21600` | Remote MCP marketplace snapshot revalidation interval in seconds (minimum `300`) | CE |
| `MCP_SELF_SERVICE_ALLOW_PRIVATE_NETWORK` | `false` | Allow users to add MCP endpoints on loopback, RFC1918, or IPv6 ULA addresses; enable only in trusted private deployments. Cloud-metadata/link-local ranges stay blocked, and users cannot submit private endpoints to the marketplace | CE |
-| `INTERNET_SEARCH_ENGINE` | `tavily` (compose default) | Internet search engine: `tavily` / `baidu` / `langsearch` | CE |
+| `INTERNET_SEARCH_ENGINE` | `tavily` (compose default) | Internet search engine: `tavily` / `baidu` / `langsearch` / `deepseek` | CE |
| `TAVILY_API_KEY` | (empty) | Tavily Search API key; required when Tavily is selected | CE |
| `BAIDU_API_KEY` | (empty) | Baidu search API key; required when Baidu is selected | CE |
| `LANGSEARCH_API_KEY` | (empty) | LangSearch Web Search API key; required when LangSearch is selected | CE |
+| `DEEPSEEK_SEARCH_API_KEY` | (empty) | DeepSeek platform API key used for the Search API; required when DeepSeek is selected | CE |
| `INTERNET_SEARCH_COUNTRY` / `INTERNET_SEARCH_AUTO_PARAMETERS` | (empty) | Tavily country ranking preference (general only; omitted when unset) / automatic parameters | CE |
| `QUERY_DATABASE_URL` | `http://your-database-api-host:6200` | HTTP backend for the data-warehouse query tool | EE (industry tool) |
| `QUERY_DATABASE_TIMEOUT_SECONDS` / `QUERY_DATABASE_RETRY_TIMES` / `QUERY_DATABASE_MAX_OUTPUT_TOKENS` | (empty) | Data-warehouse call parameters | EE |
@@ -219,13 +220,13 @@ the database has no value.
|---|---|---|
| `OPENSANDBOX_DOMAIN` | `http://opensandbox:8080` | OpenSandbox server address |
| `OPENSANDBOX_API_KEY` | Generated by CE initialization | Shared by backend and controller; never logged |
-| `OPENSANDBOX_IMAGE` | `hugagent-opensandbox-custom:latest` (compose default; code fallback `opensandbox/code-interpreter:v1.0.2`) | Sandbox runtime image; the custom image pre-installs all project dependencies (`docker/Dockerfile.opensandbox`) |
+| `OPENSANDBOX_IMAGE` | `hugagent-opensandbox-custom:latest` (compose default; code fallback `opensandbox/code-interpreter:v1.0.2`) | Sandbox runtime image; the custom image pre-installs all project dependencies (`docker/sandbox/Dockerfile.opensandbox`) |
| `OPENSANDBOX_READY_TIMEOUT_S` | `90` | Maximum wait for sandbox readiness (s) |
| `OPENSANDBOX_REQUEST_TIMEOUT_S` | `120` | Per-HTTP-call timeout (s) |
| `OPENSANDBOX_PORT` | Full repository: `8910`; CE: no mapping | Controller uses 8080 inside the network |
| `OPENSANDBOX_POOL_JUPYTER_MIN_IDLE` / `MAX_IDLE` | `.env.example` 1/3; compose defaults 2/3 | User session pool (historical setting name); CE minimum is 0 |
| `OPENSANDBOX_POOL_LIGHT_MIN_IDLE` / `MAX_IDLE` | `2` / `5` | Light bucket (one-shot executions) |
-| `OPENSANDBOX_POOL_MAX_TOTAL` | CE: `4`; full: `20` | Pool ceiling (idle + in use) |
+| `OPENSANDBOX_POOL_MAX_TOTAL` | CE: `4`; full: `20` | Controller-wide ceiling (all workers, warm and active instances); backend and controller must share this value |
| `OPENSANDBOX_SNAPSHOT_ENABLED` | `true` | Snapshot persistence master switch (snapshot+kill when idle, restore on reconnect) |
| `OPENSANDBOX_SNAPSHOT_RETENTION_DAYS` | `7` | Snapshot retention (GC sweeps expired rows) |
| `OPENSANDBOX_SNAPSHOT_WAIT_TIMEOUT_S` | `120` | Maximum poll for snapshot readiness (s) |
@@ -323,16 +324,10 @@ estimate only when it is not.
See the [License mechanism](../editions/license.md) for details.
-## Miscellaneous (rate limiting, circuit breakers, security, alerting)
+## Miscellaneous (security, alerting)
| Variable | Default | Description | Edition |
|---|---|---|---|
-| `RATE_LIMIT_ENABLED` | `true` | API rate limiting | CE |
-| `RATE_LIMIT_STORAGE` | `memory://` | Rate-limit counter store (`redis://...` supported) | CE |
-| `RATE_LIMIT_GLOBAL` / `RATE_LIMIT_PER_USER` | `500/minute` / `50/minute` | Global / per-user limits | CE |
-| `CB_USER_CENTER_THRESHOLD` / `CB_USER_CENTER_TIMEOUT` | `5` / `60` | User-center circuit breaker | EE |
-| `CB_MODEL_API_THRESHOLD` / `CB_MODEL_API_TIMEOUT` | `10` / `30` | Model-API circuit breaker | CE |
-| `CB_STORAGE_THRESHOLD` / `CB_STORAGE_TIMEOUT` | `5` / `60` | Storage circuit breaker | CE |
| `CORS_ORIGINS` | compose default `http://localhost:3000,http://localhost:5173` | Allowed cross-origin sources | CE |
| `ENABLE_SECURITY_HEADERS` | (commented; recommended `true` in production) | Security response headers | CE |
| `AUDIT_LOG_RETENTION_DAYS` / `AUDIT_LOG_EXPORT_ENABLED` | `90` / `true` (commented samples) | Audit-log retention / export | EE |
@@ -346,7 +341,7 @@ See the [License mechanism](../editions/license.md) for details.
|---|---|
| Variable samples (deployment starting point) | `.env.example` |
| Centralized settings (single read point) | `src/backend/core/config/settings.py` |
-| Compose-level injection and defaults | `docker-compose.yml`, `docker-compose.cube.yml` |
+| Compose-level injection and defaults | `docker-compose.yml`, `docker/sandbox/compose.cube.yml` |
| Sandbox provider selection | `src/backend/core/sandbox/`, `settings.py::SandboxSettings` |
| Memory settings | `settings.py::MemorySettings`, `src/backend/core/memory/` (service.py / pipeline.py) |
| Feedback GitHub configuration | `src/backend/edition_ee/services/system_config.py`, `feedback_service.py` |
@@ -395,7 +390,7 @@ Hosting remains opt-in. See [application deployment](../modules/application-host
With the module enabled, existing authenticated CE users can use Personal Agent. EE additionally checks the **Personal Agent** permission granted per user, role or team in the Config console (default off). Revoking that permission hides the entry and stops queued, scheduled and channel work. A persistent_sandbox license is not required. Both editions enforce ownership and action approvals.
-See [Personal Agent](../modules/personal-agent.md). Use docker/compose.personal-agent.yml with the root Compose file and the personal-agent profile.
+See [Personal Agent](../modules/personal-agent.md). Use docker/personal-agent/compose.personal-agent.yml with the root Compose file and the personal-agent profile.
diff --git a/document/en/deployment/offline-production.md b/document/en/deployment/offline-production.md
deleted file mode 100644
index 2b8569c1..00000000
--- a/document/en/deployment/offline-production.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Offline Production Deployment (Enterprise Edition)
-
-> Last updated: 2026-10-16 | [简体中文](../../zh-CN/deployment/offline-production.md) | Back to [Deployment Guide](README.md)
-
-Offline delivery uses compiled images and the matching deployment kit from the `deploy` branch.
-Build and package in the supplier's separate `deploy` checkout. Load the images and start the matching kit on the target.
-Branch synchronization, builds, uploads and production publication each follow the project authorization rules.
-
-## Connected side: prepare the complete delivery
-
-1. Verify the validated `deploy` commit, clean checkout, product identity and target CPU architecture.
-2. Read that commit's `deploy_kit/save_pack_ee.sh` and matching Compose files in full.
-3. Build and package:
-
- ```bash
- # Run at the root of the deploy checkout
- bash deploy_kit/save_pack_ee.sh --build
- # Package existing compiled images only after checking their source revision
- bash deploy_kit/save_pack_ee.sh
- ```
-
-The script checks compiled backend, MCP, frontend and required infrastructure images, then includes available optional images.
-The supplier must also verify every dependency of the target profiles. An absent optional image does not mean the target does not need it.
-Use image names, versions, output paths and filenames from the selected script and manifest without renaming them.
-Deliver the image archive, manifest and matching kit together. Deliver the License separately through a secure channel.
-Ordinary source images or an incremental backend/frontend pack cannot replace the complete EE delivery.
-
-## Target side: load and start
-
-Follow the delivered kit to configure a separate project name, data directories, License and protected environment files.
-Before upgrading, back up the database and files, and verify the manifest, CPU architecture and selected profiles.
-After authorization, load the specified archive and use the matching kit's startup entry and complete Compose overlays.
-For targets without registry access, verify that all required images are loaded and startup does not build images.
-Do not use the former source-deployment scripts or automatically select an unverified archive by modification time.
-
-Verify at least `/health`, `/ready`, login, attachments, model calls and the enabled sandbox capabilities.
-Database migrations modify the schema; persistent volumes do not mean upgrades cannot change business data.
-On failure, restore the recorded images, configuration and data backups without deleting data volumes.
-
-## Shipping prompt snapshots with the pack
-
-System prompts live in the database `content_blocks` table (the `prompt_versions` pool plus the `prompt_hub` template gallery). They are **not in the repository and not in the images**. To push new prompts to production, ship them as a separate data file alongside the image pack and import on the production side:
-
-### 1. Export on the connected side
-
-```bash
-python src/backend/scripts/export_content.py \
- --api-url http://localhost:3001 --only prompts
-# outputs src/backend/scripts/exported/prompts_snapshot_.json
-```
-
-(`--database-url` is also supported for direct-DB export; `ADMIN_TOKEN` is read from `.env` automatically.)
-
-### 2. Copy
-
-Copy `prompts_snapshot_.json` to the production machine together with the image tarball.
-
-### 3. Import on the production side
-
-Prerequisite: the production backend image already includes the `POST /v1/content/prompts/import` endpoint (`src/backend/api/routes/v1/content.py`). Once the new backend is up:
-
-```bash
-BACKEND_CONTAINER=your-backend-container
-docker cp prompts_snapshot_.json "${BACKEND_CONTAINER}:/tmp/"
-docker exec "$BACKEND_CONTAINER" curl -sS -X POST \
- 'http://localhost:3001/v1/content/prompts/import?overwrite=true' \
- -H "Authorization: Bearer " \
- -H 'Content-Type: application/json' \
- -d @/tmp/prompts_snapshot_.json
-```
-
-The API import invalidates the prompt cache automatically — **no backend restart needed**.
-
-> This is an on-demand, one-time data operation: the DB volume persists, so prompts do not need re-importing on every release — only when pushing new prompts to production.
-
-## Related source
-
-| Feature | Files |
-|---|---|
-| Complete EE image pack (deploy branch) | `deploy_kit/save_pack_ee.sh` |
-| Compiled EE images (deploy branch) | `docker/Dockerfile.backend-compiled`, `docker/Dockerfile.mcp-compiled` |
-| Compose overlays (deploy branch) | `docker-compose.prod.yml`, `docker-compose.deploy.yml`; runtime follows the delivered kit |
-| Prompt export / import | `src/backend/scripts/export_content.py`, `src/backend/scripts/import_content.py` |
-| Prompt version pool | `src/backend/core/services/prompt_version_service.py` |
diff --git a/document/en/deployment/quick-install.md b/document/en/deployment/quick-install.md
index 04d1175b..78bf3e1d 100644
--- a/document/en/deployment/quick-install.md
+++ b/document/en/deployment/quick-install.md
@@ -42,7 +42,7 @@ The installer will:
3. Detect and install `bubblewrap` on Linux, or verify the system `sandbox-exec` on macOS; installation fails when the sandbox backend is unavailable instead of falling back to raw execution;
4. Detect optional LibreOffice and, when it is missing, explain the unavailable features and ask whether to install it; skipping it or a failed install doesn't block the remaining features;
5. Create a virtual environment at `~/.hugagent/venv` (using [uv](https://github.com/astral-sh/uv) when available, or `python -m venv` otherwise), and rebuild an incomplete environment left by an interrupted run;
-6. Install `requirements.txt`, the `hugagent` console command, the built-in Agent Skills Python and Node.js dependencies, and optional local knowledge-base dependencies;
+6. Install `requirements.txt`, the `hugagent-server` console command, the built-in Agent Skills Python and Node.js dependencies, and optional local knowledge-base dependencies;
7. Build the frontend at `src/frontend/dist`;
8. Enter the interactive first-run wizard.
@@ -75,8 +75,8 @@ restore a plugin that a user deliberately removed.
**Step 4 (optional) · File parser** — parsing uploaded PDFs / scanned documents needs an external parser service (MinerU-compatible); enter its API URL to enable it (written to `file_parser.api_url`), or press Enter to skip. Excel / CSV / PPTX / text parse in-process and need none of this.
**Step 5 (optional) · Internet search** — agent web search needs a search
-engine key. Select `tavily` (default), `baidu` (Qianfan AppBuilder), or
-`langsearch`, and enter the API key for the selected engine. Press Enter to
+engine key. Select `tavily` (default), `baidu` (Qianfan AppBuilder),
+`langsearch`, or `deepseek`, and enter the API key for the selected engine. Press Enter to
skip this step. You can also configure it later under **Settings → System →
Service Config**.
@@ -84,7 +84,7 @@ At the end the wizard prints a **host-capability summary** (whether Node.js, pan
> **Warning:** The server listens on `127.0.0.1` by default. If the server must
> accept remote connections, run
-> `hugagent serve --host 0.0.0.0 --port 3001 --no-browser`, and configure a
+> `hugagent-server serve --host 0.0.0.0 --port 3001 --no-browser`, and configure a
> strong administrator password, a firewall, and HTTPS first. Don't expose the
> service directly on an untrusted network.
@@ -93,7 +93,7 @@ At the end the wizard prints a **host-capability summary** (whether Node.js, pan
`onboard` accepts bypass flags for scripted installs:
```bash
-hugagent onboard \
+hugagent-server onboard \
--username admin --password '' \
--model-base-url https://api.deepseek.com/v1 \
--model-api-key '' --model-name deepseek-chat \
@@ -109,10 +109,10 @@ hugagent onboard \
## Daily use
```bash
-hugagent # initialized → start server and open browser; not initialized → enter the wizard
-hugagent serve # start explicitly (--host to change bind address, --port to change port)
-hugagent onboard # re-run the wizard / change configuration
-hugagent doctor # environment self-check (Python version, port availability, data dir, frontend build, deps, …)
+hugagent-server # initialized → start server and open browser; not initialized → enter the wizard
+hugagent-server serve # start explicitly (--host to change bind address, --port to change port)
+hugagent-server onboard # re-run the wizard / change configuration
+hugagent-server doctor # environment self-check (Python version, port availability, data dir, frontend build, deps, …)
```
## Data directory
@@ -166,16 +166,16 @@ The no-Docker single-machine mode is built to be lightweight. Here is how it dif
| Symptom | Fix |
|---|---|
-| Startup reports the port is in use | `hugagent serve --port `; or run `hugagent doctor` first to check |
+| Startup reports the port is in use | `hugagent-server serve --port `; or run `hugagent-server doctor` first to check |
| The page shows JSON instead of the app | Frontend not built: `cd src/frontend && npm run build`, or set `FRONTEND_DIST_DIR` to a built `dist` |
-| Login reports the model is unavailable | Re-run `hugagent onboard` to reconfigure the model (the wizard tests connectivity) |
+| Login reports the model is unavailable | Re-run `hugagent-server onboard` to reconfigure the model (the wizard tests connectivity) |
| Startup logs repeatedly report `AllocTimestamp` / `Method not implemented` | Stop the service and rerun the public one-command installer. The installer reconciles PyMilvus and Milvus Lite to compatible versions without deleting `~/.hugagent/milvus.db`. |
-| Want to switch model / change config | Re-run `hugagent onboard`, or log in and adjust under Settings → System → Model Services / Service Config |
+| Want to switch model / change config | Re-run `hugagent-server onboard`, or log in and adjust under Settings → System → Model Services / Service Config |
| PPT/Word preview reports that LibreOffice isn't installed | Re-run the one-command installer and choose to install it when prompted. On Debian/Ubuntu, you can instead run `sudo apt-get update && sudo apt-get install -y libreoffice-impress libreoffice-writer libreoffice-calc`, then restart HugAgentOS. |
| bash reports a missing OS sandbox runner | Install `bubblewrap` on Linux (`sudo apt-get install bubblewrap` on Debian/Ubuntu), then restart. Until it is installed, every preset except *Full access* refuses host commands outright — by design, rather than running them unconfined. |
| bash reports that this system cannot enforce the policy | The preset asks for isolation beyond what this platform provides — the Windows low-integrity sandbox cannot narrow reads, and cannot cut the network without administrator rights. Relax the corresponding setting (for example move the *network* category from *block* back to *confirm*), or use a platform that offers the capability. |
-| Skill execution repeatedly reports `fork: Resource temporarily unavailable` | Stop the current service, rerun the public installer to upgrade, and start `hugagent` again. If an older version left child processes behind, inspect processes owned by the current user and, when needed, sign out of the login session before retrying. |
-| Is the environment ready | `hugagent doctor` runs a one-shot self-check |
+| Skill execution repeatedly reports `fork: Resource temporarily unavailable` | Stop the current service, rerun the public installer to upgrade, and start `hugagent-server` again. If an older version left child processes behind, inspect processes owned by the current user and, when needed, sign out of the login session before retrying. |
+| Is the environment ready | `hugagent-server doctor` runs a one-shot self-check |
## Related source
@@ -185,7 +185,7 @@ The no-Docker single-machine mode is built to be lightweight. Here is how it dif
| CLI (onboard / serve / doctor) | `src/backend/cli.py` |
| Local-mode switch | `src/backend/core/config/settings.py` (`DeploySettings`, `DEPLOY_PROFILE=local`) |
| Frontend static hosting + `/api` bridge | `src/backend/api/local_hosting.py` |
-| MCP / sandbox subprocess supervision | `src/backend/orchestration/local_subprocess.py` |
-| In-process event log / ephemeral state | `src/backend/orchestration/run_event_stream.py`, `src/backend/core/infra/ephemeral.py` (enabled by leaving `REDIS_URL` empty) |
-| Built-in MCP catalog seed | `src/backend/core/services/mcp_service.py` (`seed_builtin_mcp_servers_if_empty`) |
+| MCP / sandbox subprocess supervision | `src/backend/core/infra/local_runtime/` |
+| In-process event log / ephemeral state | `src/backend/core/runs/events/`, `src/backend/core/infra/ephemeral.py` (enabled by leaving `REDIS_URL` empty) |
+| Built-in MCP catalog seed | `src/backend/core/capabilities/mcp/service.py` (`seed_builtin_mcp_servers_if_empty`) |
| Environment variables | [environment-variables.md](environment-variables.md) (the "No-Docker local mode" section) |
diff --git a/document/en/deployment/space-files.md b/document/en/deployment/space-files.md
index 6e2e1b55..5c696ccb 100644
--- a/document/en/deployment/space-files.md
+++ b/document/en/deployment/space-files.md
@@ -25,7 +25,7 @@ The controller needs no host FUSE mount or shared-propagation directory.
## Local S3 service (CE / EE)
-Add docker/compose.object-storage.yml to COMPOSE_FILE.
+Add docker/object-storage/compose.object-storage.yml to COMPOSE_FILE.
Set S3_ENDPOINT=http://object-storage:9000 and S3_BUCKET=hugagent-files.
Generate separate random S3_ACCESS_KEY and S3_SECRET_KEY values.
Store credentials in a protected environment file with mode 600.
@@ -36,15 +36,11 @@ Before switching an existing deployment, back up configuration and database.
Copy valid file objects and attachments. Check each object's SHA-256.
Keep source files. Switch STORAGE_TYPE and recreate the application containers after verification.
-## File import
+## Existing file migration
-Back up data and pause writes. Preview one user's or team's import plan:
-
- PYTHONPATH=src/backend python -m scripts.import_space_files --user USER_ID
- PYTHONPATH=src/backend python -m scripts.import_space_files --team TEAM_ID
-
-Review destinations and conflicts. Add --apply to import.
-The tool verifies content and preserves source objects.
+The standalone file import command has been removed. Back up the database and objects,
+pause destination writes, and review a migration plan before moving existing files.
+Check destinations, name conflicts, and content. Preserve source objects and references.
## Verification
@@ -52,8 +48,6 @@ Test upload, download and listing with an editor account.
Confirm that a read-only account cannot write or delete.
Revoke team access and confirm that the directory is unavailable.
-Local S3 has passed isolated MinIO, controller, sandbox and browser tests.
-Live OSS and OBS mounts need verification in dedicated test buckets.
Object-store directory moves use multiple object operations. They are not atomic filesystem renames.
## File page
@@ -105,4 +99,4 @@ Missing, expired, or changed-argument tickets cannot send a gateway request.
Desktop rollout requires the bundled backend, cloud backend, and a refreshed
capability manifest.
-The object-storage image uses checksum-pinned binaries from the official MinIO GitHub release. Docker builds this image from docker/Dockerfile.object-storage. Existing Milvus images and volumes are separate.
+The object-storage image uses checksum-pinned binaries from the official MinIO GitHub release. Docker builds this image from docker/object-storage/Dockerfile.object-storage. Existing Milvus images and volumes are separate.
diff --git a/document/en/deployment/uos1070-desktop.md b/document/en/deployment/uos1070-desktop.md
deleted file mode 100644
index 89017148..00000000
--- a/document/en/deployment/uos1070-desktop.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# UOS 1070 aarch64 Desktop Client
-
-> [简体中文](../../zh-CN/deployment/uos1070-desktop.md)
-
-UOS 1070 aarch64 uses the Electron 43 release line in `desktop-uos/`. The supported
-delivery format is a Debian ARM64 `.deb`. It does not depend on system WebKitGTK 4.1,
-so it avoids the Tauri 2 WebView dependency blocker on the Debian 10-era UOS base.
-
-## Support baseline
-
-| Item | Production baseline |
-|---|---|
-| Operating system | UOS 1070 Desktop, aarch64 |
-| libc | glibc 2.28 |
-| Desktop shell | Electron 43.4.1 |
-| Installer | Debian `arm64` `.deb` |
-| Local runtime | Private CPython 3.11 with an `aarch64-manylinux_2_28` lock |
-| Data | `~/.hugagent` |
-| Configuration | `~/.config/com.hugagent.desktop` |
-
-The full package supports local, cloud, and dual modes with the existing desktop
-capabilities: deep-link login, loopback streaming proxy, tray/quick ask, native
-notifications, local-folder projects, dual-upstream routing, identity/model bridges,
-offline local-service install/upgrade/rollback, and SHA-256 verified updates.
-
-UOS hides the in-window menu bar by default and retains native title-bar controls.
-The tray exposes File, Edit, View, and Help commands; Ctrl+Shift+N opens a new window
-and Ctrl+N starts a new chat. The workspace shares macOS navigation gradients,
-rounded sidebar/content surfaces, light/dark themes, and loading layout. Quick ask
-keeps its compact appearance. The native UOS titlebar sits outside the web area,
-so no additional top inset is reserved inside the page.
-
-A distribution brand may preset the first-launch mode for a full package. A fixed
-dual-mode build goes directly to one-action initialization. The initialization and
-installation pages use the same halo, orbit, and progress-highlight motion as the
-existing desktop client, while showing only the current stage, percentage, and
-actionable errors. Raw logs remain in the local-service log directory for diagnosis
-instead of appearing at the bottom of the page.
-
-## Build and verification
-
-Build the full package on native Linux aarch64, preferably UOS 1070 itself. An x86_64
-builder may only cross-package the remote-only shell without the Python runtime.
-
-```bash
-cd desktop-uos
-npm install
-npm test
-npm run build
-npm run verify:deb -- "dist/HugAgentOS UOS_0.5.15_uos1070_arm64.deb"
-```
-
-Release acceptance must cover:
-
-1. Debian architecture is `arm64`;
-2. the Electron ELF imports no GLIBC symbol newer than 2.28;
-3. a disconnected full-package cold start performs no pip, uv, or Python download;
-4. local, cloud, and dual modes pass login, SSE chat, file transfer, folder selection, and notifications;
-5. dual-mode cloud projects remain in the cloud while local-folder projects remain local;
-6. a staged update passes SHA-256 verification, system authorization, installation, and restart.
-
-UOS shares one release index with Windows and macOS: each client fetches the update for its
-own platform and reports "no update available for this platform" when none was published,
-while older clients that send no platform read the common release, which advances only once
-all three platforms have published that version. The desktop version is identical across the
-three platforms and the build enforces it.
-
-See [`desktop-uos/README_EN.md`](../../../desktop-uos/README_EN.md) for commands,
-artifacts, and publishing.
-
-> Electron 43 upstream support is scheduled to end in January 2027. Treat it as the
-> current UOS compatibility baseline, keep the latest 43.x patch deployed, and qualify
-> Electron 44+ continuously.
-
-## Parity with the current Tauri client
-
-UOS shares the current React frontend and CE backend sources. Rebuilding the full package includes
-the latest capability origins, local enablement, skill sync and working-directory behavior.
-Updating the cloud alone does not replace resources in an installed client.
-
-- File supports opening a folder into a local project conversation; ordinary packages can change runtime mode.
-- View offers Zoom In, Zoom Out and Actual Size (Ctrl +/-/0 or Ctrl+wheel). Tauri's zoom steps are
- saved in `prefs.json` and reused by new windows and after restart; system DPI remains native.
-- Help, tray and sidebar share update handling. Background release notices only offer an update;
- confirmation opens download progress with cancellation during download. Installation still requires
- Ed25519 signature and SHA-256 verification and UOS system authorization.
- Older servers use low-frequency checks; reconnects back off and concurrent checks cannot install twice.
-- The loopback proxy checks Origin/Fetch Metadata before injecting session and bridge credentials;
- direct requests without source metadata are refused.
-- Service logs rotate while running, keeping the current and previous files at up to 32 MiB each.
- Capability readiness checks back off from 500 ms to 15 seconds. Existing UOS data and capability
- directories are preserved without automatically migrating configuration.
-
-Full packages also support Tauri's hybrid-only build option:
-
-```bash
-JX_DEFAULT_SERVER_BASE=https://agent.example.com npm run build:hybrid-only
-```
-
-This fixes the cloud address and dual mode and requires a cloud address. Thin packages cannot be
-hybrid-only. Full offline ARM64 packages still require native aarch64 build and installation validation.
diff --git a/document/en/development/backend.md b/document/en/development/backend.md
index 3ed2f541..153eac46 100644
--- a/document/en/development/backend.md
+++ b/document/en/development/backend.md
@@ -41,7 +41,7 @@ The following targets are taken from the root `Makefile`:
| `make migrate` | `alembic upgrade head` |
| `make migrate-new msg="..."` | `alembic revision --autogenerate -m "..."` |
| `make migrate-down` / `migrate-history` | roll back one step / show history |
-| `make db-reset` / `db-seed` | reset (destructive) / seed sample data |
+| `make db-reset` | Reset the database (destructive) |
| `make build` / `up` / `down` / `logs` / `ps` etc. | docker-compose wrappers |
> ⚠️ The committed tree is not formatter-clean: **do not run black/isort over files you did not touch** (it buries the real diff in formatting noise); format only the scope you changed.
@@ -68,10 +68,10 @@ The version list pins a compatible mem0, pymilvus and Milvus Lite combination. D
## Alembic migration flow
-1. Change the ORM models in `src/backend/core/db/models.py`;
+1. Change the ORM models in `src/backend/core/db/models/`;
2. `make migrate-new msg="add xxx table"` to autogenerate the migration (**review the output by hand** — autogenerate cannot be fully trusted);
3. Apply with `make migrate` (or `alembic upgrade head` inside the container);
-4. If the table is **EE-only**: add its name to `core/db/edition_tables.py::EE_ONLY_TABLES` (CE does not create empty EE tables; names in the set are existence-asserted).
+4. If the table is **EE-only**: add its name to `edition_ee/db/edition_tables.py::EE_ONLY_TABLES` (CE does not create empty EE tables; names in the set are existence-asserted).
Note: the main-repo alembic chain is the EE chain; the CE derived tree uses its own `ce_0001` baseline chain (see [CE Build Pipeline](../editions/build-ce.md#ce-database-differences)) — new main-repo migrations never enter CE.
@@ -175,7 +175,7 @@ PYTHONPATH=src/backend python -m extensions.mcp_servers._mcp._selftest
## Adding a skill (Agent Skill)
-Skill loading is multi-source (`core/agent_skills/config.py`): built-in (`extensions/skills/default/`, always-on), admin (DB / `/app/storage/admin_skills/`), user, and project sources merge by priority. `extensions/skills/marketplace/` holds install-based marketplace seeds scanned separately by the marketplace service — it is **not** part of the default loading sources.
+Skill loading is multi-source (`core/capabilities/skills/config.py`): built-in (`extensions/skills/default/`, always-on), admin (DB / `/app/storage/admin_skills/`), user, and project sources merge by priority. `extensions/skills/marketplace/` holds install-based marketplace seeds scanned separately by the marketplace service — it is **not** part of the default loading sources.
Adding a **built-in skill**:
@@ -188,7 +188,7 @@ A **marketplace skill** goes under `extensions/skills/marketplace//` w
## New-feature checklist
-- [ ] ORM model + indexes + timestamps (`core/db/models.py`); EE tables added to `EE_ONLY_TABLES`
+- [ ] ORM model + indexes + timestamps (`core/db/models/`); EE tables added to `EE_ONLY_TABLES`
- [ ] Repository filters soft deletes; Service carries business logic and permission checks
- [ ] Routes use envelope responses; errors go through `core/infra/exceptions`
- [ ] Route registered in `CE_ROUTERS` / `EE_ROUTERS` (EE with a feature bit + manifest exclusion)
@@ -205,10 +205,10 @@ A **marketplace skill** goes under `extensions/skills/marketplace//` w
| Response envelope | `src/backend/core/infra/responses.py` |
| Exception hierarchy | `src/backend/core/infra/exceptions.py` |
| Dependency injection | `src/backend/api/deps.py` |
-| ORM / table boundary | `src/backend/core/db/models.py`, `src/backend/core/db/edition_tables.py` |
+| ORM / table boundary | `src/backend/core/db/models/`, `src/backend/edition_ee/db/edition_tables.py` |
| MCP port table / connection config | `src/backend/extensions/mcp_servers/_ports.py`, `src/backend/core/config/mcp_config.py` |
| Capability catalog | `src/backend/core/config/catalog.json` |
-| Skill loading | `src/backend/core/agent_skills/` (`config.py` / `loader.py` / `registry.py`) |
-| Workflow orchestration | `src/backend/orchestration/workflow.py` |
+| Skill loading | `src/backend/core/capabilities/skills/` (`config.py` / `loader.py` / `registry.py`) |
+| Workflow orchestration | `src/backend/core/agent_runtime/runner.py` |
See also: [Frontend Development Guide](frontend.md) · [API Overview](../api/overview.md) · [MCP Tools](../modules/mcp-tools.md) · [Agent Skills](../modules/agent-skills.md)
diff --git a/document/en/development/container-layout.md b/document/en/development/container-layout.md
deleted file mode 100644
index 9911e7f1..00000000
--- a/document/en/development/container-layout.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# Container-responsive layout
-
-The chat toolbar responds to its pane's available width. Home, conversation, project,
-and split views share the same layout.
-
-- Tools and model/send controls form separate groups and wrap when necessary.
-- Labels collapse progressively while icons, accessible names, and mode exits remain.
- Model names stay visible with ellipsis for long text.
-- Touch devices use real 44px targets in normal flow, without overlapping invisible hit layers.
-- Project, capability, and knowledge cards use intrinsic grids; long names cannot widen tracks.
-- Safe areas, page height, and portaled menus retain their viewport-based constraints.
-
-Implementation: frontend components/chat/ComposerToolbar.tsx,
-styles/chat/toolbar-layout.css, styles/projects.css, and styles/catalog/.
-A container supplies width and its descendants respond. Do not query the container
-from itself or duplicate CSS breakpoints in JavaScript.
-
-From the WSL project directory, enter src/frontend with a supported Node.js version:
-
- npm run test:container-layout-browser
- npm run test:canvas-layout-browser
- npm run test:chat-width-browser
- npm run lint
- npm run build
-
-The new suite covers narrow containers inside a wide window, phone viewports,
-fractional boundaries, light/dark themes, long names, all modes, touch targets,
-keyboard exits, and send/stop callbacks.
diff --git a/document/en/development/frontend.md b/document/en/development/frontend.md
index 9df68e6b..715a69c5 100644
--- a/document/en/development/frontend.md
+++ b/document/en/development/frontend.md
@@ -132,6 +132,15 @@ Caveats:
- When adding new EE UI: ① gate it with the selector on the matching feature bit; ② if the entire page is EE (like /admin, /config), no runtime gating is needed — the CE derived tree physically excludes the component directories and removes the routes via the overlay `main.tsx` (see [CE Build Pipeline](../editions/build-ce.md));
- When handling API errors, branch on `LicenseError` (402) and show "contact your administrator to update the license" rather than a generic failure message.
+## Container-responsive layout
+
+The chat toolbar and project / capability / knowledge cards lay out by the available width of their pane; home,
+conversation, project and split views share the same rules. Containers provide width and descendants respond to the
+container; elements must not query themselves, and CSS breakpoints must not be duplicated in JavaScript. Touch targets
+are real 44px areas. Entry points: `components/chat/ComposerToolbar.tsx`, `styles/chat/toolbar-layout.css`,
+`styles/projects.css`, `styles/catalog/`. After changes, run `npm run test:container-layout-browser` and
+`npm run test:chat-width-browser`.
+
## New-feature checklist
- [ ] Components are PascalCase + named export + Props interface
diff --git a/document/en/development/workbench-migration.md b/document/en/development/workbench-migration.md
index 1449f3fd..89d6a38e 100644
--- a/document/en/development/workbench-migration.md
+++ b/document/en/development/workbench-migration.md
@@ -44,24 +44,7 @@ The SDK CLI validates declarations. It does not migrate databases or business da
# Workbench configuration migration and recovery
-Migration is an explicit operator action. It never runs during startup. Rehearse on an isolated database copy before separately authorizing a real migration. The current command accepts local rehearsal copies only.
-
-## Inventory and cutover
-
-1. Freeze every writer on the copy, including content imports, preferences, plugin installation, and background jobs. --writes-frozen confirms this operator action; it does not stop services.
-2. Set WB20_COPY_DATABASE_URL through protected configuration. SQLite must reside under the current checkout's .scratch/workbench-20 directory. PostgreSQL must use a loopback host and a database name beginning with workbench_20. Never target business data.
-3. Create a private 0700 snapshot directory. Run:
-
-```bash
-PYTHONPATH=src/backend python -m scripts.workbench_migration inventory \
- --snapshot /absolute/private/path/before.json
-PYTHONPATH=src/backend python -m scripts.workbench_migration apply \
- --snapshot /absolute/private/path/before.json --digest --writes-frozen
-```
-
-Inventory never writes the database. Reports contain counts, contract versions, redacted locations, and error codes. They omit URLs, user identities, tickets, and secrets. The 0600 snapshot retains all migration inputs: configuration, preferences, role/team relationships, plugin declarations, and workbench records. It does not replace a full database or business-file backup.
-
-A matching digest and unchanged source permit one transactional cutover. Failures roll back the transaction. A workbench_migration:v1 receipt records both snapshot digests. Cutover removes the old app_config row and navigation lists. The directory reads canonical sources. Duplicate external IDs, unknown IDs, dangerous URLs, and unknown fields are quarantined without creating entries or grants. Protected snapshots retain their raw values.
+The standalone migration command has been removed. The following sections describe internal migration and recovery constraints; they are not an executable operator procedure. Back up existing deployments, prepare a version-specific plan, validate it on an isolated copy, and obtain separate authorization before execution.
## Preserved behavior
@@ -69,7 +52,7 @@ A matching digest and unchanged source permit one transactional cutover. Failure
- External ordering and enabled state survive. Default navigation becomes layout policy. Existing modern preferences win. Confirmed old preferences.pinned_apps/app_order migrate to web preferences: null inherits and an empty list explicitly unpins. Chat order, theme, and unrelated preferences remain unchanged.
- Unmapped permission references remain unchanged and are reported. They do not grant access to new identities. Plugin UI v1/v2 is not upgraded to v3. Names and slugs never substitute for exact installations.
- Existing workbench_external_auth:legacy: bindings retain their keys. Missing target adapters produce auth_configuration_required. Administrators must approve explicit login modes. Real legacy targets remain unverified until exercised. Generic SSO token forwarding must never return.
-- Same-service recovery retains workbench_origin_identity. A service clone requires a fresh inventory and clone-origin on the copy. Existing plugin keys do not automatically grant access to the new origin. Review cross-origin state and preferences separately.
+- Same-service recovery retains workbench_origin_identity. A service clone requires a fresh inventory and a regenerated service origin identity on the copy. Existing plugin keys do not automatically grant access to the new origin. Review cross-origin state and preferences separately.
## External compatibility window
@@ -77,13 +60,7 @@ content-v1 remains supported throughout workbench protocol 1.x. Announce a separ
## Recovery and incremental writes
-Freeze every writer again:
-
-```bash
-PYTHONPATH=src/backend python -m scripts.workbench_migration rollback \
- --snapshot /absolute/private/path/before.json --digest \
- --delta /absolute/private/path/delta.json --writes-frozen
-```
+Freeze all writers and follow the reviewed recovery plan.
Recovery first exports a protected delta. Changes to already-mapped ordinary legacy links can replay into the old format. New identities, application state, resources, permissions, plugins, authentication, or policy changes block unsafe downgrade with delta_not_representable. New data stays intact. Never restore only the old snapshot. Keep the verified newer version and review the delta. Recovery receipts are idempotent. A restored batch requires a new reviewed batch before another apply.
diff --git a/document/en/development/workbench-routing.md b/document/en/development/workbench-routing.md
index 33aa5420..a7c484f6 100644
--- a/document/en/development/workbench-routing.md
+++ b/document/en/development/workbench-routing.md
@@ -10,6 +10,6 @@ The page distinguishes directory loading, directory failure, an undiscoverable a
A secondary view may reference its application's workspace module. Launch accepts only the main module and modules declared by that application's views. Other installation modules are rejected.
-No database schema migration is required. Legacy UI v1/v2, managed application APIs, and existing business URLs retain their contracts. Integrated acceptance records full platform validation. Fixture checks do not establish native client compatibility.
+No database schema migration is required. Legacy UI v1/v2, managed application APIs, and existing business URLs retain their contracts.
An empty directory retains the deep link and shows an empty state. Network and server failures offer retry. They do not appear as missing applications or denied discovery.
diff --git a/document/en/editions/build-ce.md b/document/en/editions/build-ce.md
index 5921ec28..15150f24 100644
--- a/document/en/editions/build-ce.md
+++ b/document/en/editions/build-ce.md
@@ -26,7 +26,7 @@ The manifest has the following sections, in processing order:
Glob patterns (relative to the repo root); a match means the file is never copied. Covers:
-- **Backend EE modules**: the complete `edition_ee/**` implementation root (Team/RBAC, SSO, license verification and gate, EE ORM, Dify integration), cloud storage (`core/storage/s3.py`, `oss.py`), the Cube sandbox provider, memory audit, skill distillation, and other EE services;
+- **Backend EE modules**: the complete `edition_ee/**` implementation root (Team/RBAC, SSO, license verification and gate, EE ORM, Dify integration), cloud storage (`core/files/storage/s3.py`, `oss.py`), the Cube sandbox provider, memory audit, skill distillation, and other EE services;
- **EE routes**: `api/routes/v1/admin_*.py`, `config_*.py`, `audit.py`, `auth.py`, `team_files.py`, `service_configs.py`, `data_sources.py`, `db_metadata.py`, `gateway_*.py`;
- **Industry MCP servers**: `extensions/mcp_servers/query_database_mcp/**`, `ai_chain_information_mcp/**`;
- **Industry-specific frontend code and seeds**: the industry-chain Canvas,
@@ -83,13 +83,13 @@ Industry-chain, news, and company-profile presentation remains an EE feature.
| `.hugagent-edition` | Machine-readable `ce` marker used only after derivation; it lets release tooling distinguish a derived CE checkout from a source checkout whose generator is unexpectedly missing |
| `.github/workflows/desktop-release.yml` | Public CE desktop release workflow, including the release-tag/version preflight gate |
| `src/backend/api/routes/v1/__init__.py` | CE route registry; `EE_ROUTERS` is always empty |
-| `src/backend/core/auth/permissions_iface.py` | Owner-only single-tenant authorization interface with no team permission exports |
-| `src/backend/core/services/artifact_edition.py` | Personal artifact-scope interface with no team fields, permissions, or repository methods |
+| `src/backend/core/auth/personal_permissions_iface.py` | Owner-only single-tenant authorization interface with no team permission exports |
+| `src/backend/core/files/edition.py` | Personal artifact-scope interface with no team fields, permissions, or repository methods |
| `src/backend/core/llm/tools/edition_{myspace,myspace_vfs,artifact_recovery}.py` | Personal MySpace tool, VFS, and recovery interfaces; organization implementations do not enter CE |
-| `src/backend/core/config/edition_display_names.py` | CE tool display names with no team-tool names |
+| `src/backend/core/config/display_names.py` | CE tool display names with no team-tool names |
| `src/backend/core/memory/audit.py` | Memory-audit no-op stub (same interface, writes nothing) |
| `src/backend/alembic/versions/ce_000{1,2}_*.py` | CE independent migration-chain baseline and idempotent schema upgrade (next section) |
-| `src/backend/core/services/edition_startup.py` | CE startup seam with no-op implementations for EE data-source recovery and distillation scheduling, so pruned modules are never imported |
+| `src/backend/orchestration/workers/edition_startup.py` | CE startup seam with no-op implementations for EE data-source recovery and distillation scheduling, so pruned modules are never imported |
| `src/backend/tests/ce_release/` | Executable CE release regressions for login, private skills/MCP, database upgrades, startup, and Compose contracts |
| `src/backend/api/routes/v1/{agents,content,kb_models,projects}.py` | CE API contracts with administration endpoints and organization fields removed |
| `src/backend/extensions/mcp_servers/_ports.py` | Port table for the 7 general tools (EE industry-tool and retired ports marked reserved) |
@@ -194,7 +194,7 @@ A qualifying release build must pass all of:
| Overlay directory | `ce/overlay/` |
| Derived-tree edition marker | `ce/overlay/.hugagent-edition` |
| Public desktop release workflow | `ce/overlay/.github/workflows/desktop-release.yml` |
-| CE/EE table boundary | `src/backend/core/db/edition_tables.py` |
+| CE/EE table boundary | `src/backend/edition_ee/db/edition_tables.py` |
| Idempotent schema reconciler | `src/backend/core/db/schema_reconcile.py` |
| Startup table-creation CE branch | `src/backend/core/db/engine.py::init_db` |
| CE independent migration chain | `ce/overlay/src/backend/alembic/versions/ce_000{1,2}_*.py` |
diff --git a/document/en/getting-started/introduction.md b/document/en/getting-started/introduction.md
index 10859626..74117c6e 100644
--- a/document/en/getting-started/introduction.md
+++ b/document/en/getting-started/introduction.md
@@ -1,93 +1,123 @@
# Introduction
-> Last updated: 2026-07-02
+> Last updated: 2026-10-23 | [简体中文](../../zh-CN/getting-started/introduction.md)
-HugAgentOS is an enterprise-grade AI Agent platform. Built around a ReAct agent core, it brings conversation, tool calling (MCP), skills, sandboxed code execution, long-term memory, knowledge-base RAG, automation, and batch processing together in a fully containerized (Docker Compose) system. The backend runs on FastAPI + AgentScope 2.0, the frontend on React 19 + Vite + Zustand, and every capability is registered in a unified catalog that can be toggled at will. Out of the box it is a complete, self-hostable product — not a half-finished framework.
+HugAgentOS is an open-source, self-hosted **agent harness**. The model does the thinking; the
+harness handles everything around it: where a task runs, how it picks up after an interruption,
+how tools and sandboxes are used, which actions need human approval, and how experience is kept.
-The platform ships in two editions: the **Community Edition (CE, open source)** and the **Enterprise Edition (EE, commercial)**. CE targets individuals and small teams and includes the full conversation, tool, skill, sandbox, three-layer memory, automation, batch, and data-canvas stack. EE layers organization-scale capabilities on top: team collaboration, SSO, audit & compliance, industry data tools, persistent sandboxes, cloud storage, and the full admin console. See [Edition Comparison](../editions/overview.md) for the exact boundary.
+At its core is the **durable agent**: your request runs in the cloud, not in your browser or on
+your computer. Close the tab or shut the laptop and the task keeps moving; if a server restarts or
+fails, another instance continues from the last saved progress; external operations that may or
+may not have happened are never redone automatically. On top of that, HugAgentOS provides an
+always-on **Personal Agent** and runs in two forms: **HugAgentOS Cloud** and **HugAgentOS Local**.
+
+The platform ships as a **Community Edition (CE, open source)** and an **Enterprise Edition (EE)**.
+CE lets an individual run the complete harness and Personal Agent; EE adds organization-scale
+capabilities such as team collaboration, SSO, compliance auditing and industry data tools. See the
+[edition comparison](../editions/overview.md) for the exact boundary.
+
+## Runtime forms
+
+| Form | Runs on | Data and state | Best for | Guide |
+|---|---|---|---|---|
+| **HugAgentOS Cloud** | Your server or private cloud | PostgreSQL, Redis, S3-compatible storage, OpenSandbox | 24/7 Personal Agent, long tasks, IM channels, access from any device | [Docker Compose](../deployment/docker-compose.md) |
+| **HugAgentOS Local** | Your computer (Windows / macOS / Linux) | Local SQLite, Milvus Lite, local files | Offline work, sensitive data, working directly on local projects | [Desktop local mode](../deployment/desktop-local-mode.md) |
+| **Local + cloud** | Cloud as control plane, local machine as execution plane | Cloud projects in the cloud, local projects on the machine | An always-on cloud agent that also operates your computer and projects | [Desktop local mode](../deployment/desktop-local-mode.md) |
+| One-command install | A development machine | SQLite, in-process state, subprocess sandbox | Trials and development | [No-Docker quick install](../deployment/quick-install.md) |
+
+Durable 24/7 operation needs HugAgentOS Cloud. The one-command install has no cross-process lease
+takeover and is meant for trials only.
## Feature overview
| Capability | Description | Edition |
|---|---|---|
-| Conversational agent | SSE streaming chat, AgentScope 2.0 ReActAgent, Plan Mode sub-agent, deep thinking, `[ref:tool-N]` citation tracing, automatic conversation-title summarization | CE |
-| Sub-agents | User-created sub-agents, @mention collaboration, routing strategy (`ROUTER_STRATEGY`) | CE (agent versioning / org-level agent library: EE) |
-| MCP tool ecosystem | 9 built-in MCP servers (7 CE general tools: internet search, web fetch, chart generation, batch planning, automation task management, skill management, knowledge retrieval; 2 EE industry tools: data-warehouse query and industry-chain info), served from a dedicated `mcp` container over streamable-http; users can self-register remote HTTP/SSE MCP servers | CE (industry tools such as industry-chain analysis, company profiling, data-warehouse query: EE) |
-| Skill system | Agent Skills (SKILL.md + scripts): built-in skill bundles, admin upload, skill marketplace browse & install, skill distillation | CE (skill review / org governance: EE) |
-| Sandbox execution | `bash` / `sandbox_put_artifact` / `sandbox_get_artifact` tools with three switchable providers: script_runner (lightweight, built-in), OpenSandbox (persistent sessions + Jupyter context + snapshots), CubeSandbox (E2B-compatible MicroVM) | CE for the lightweight sandbox; persistent sandboxes (session keep-alive / snapshots): EE |
-| Memory system | mem0 three-layer memory: L1 user profile, L2 vector memory (Milvus), L3 knowledge graph (Neo4j), with cross-session injection and background extraction | CE (memory audit: EE) |
-| Knowledge-base RAG | Document upload, chunking, hybrid vector + keyword retrieval, and private knowledge bases; EE can add public and external Dify knowledge bases | CE (public KB and Dify integration: EE) |
-| Automation | Scheduled tasks / cron scheduling / prompt & plan automation / failure retry (`orchestration/schedulers/`) | CE |
-| Data canvas | Online spreadsheet editing built on Univer | CE for personal editing; real-time multi-user collaboration: EE |
-| Projects / My Space | Personal projects (file capacity quota), personal drive (myspace), favorites, conversation sharing | CE (team folders / permission matrix: EE) |
-| Admin consoles | `/admin` content management (skills, capability center, release notes) + `/config` system console (prompt version pool, models, users/teams, billing, audit, security) | CE basics; full console (users/teams/billing/audit/security): EE |
-
-## CE vs. EE
-
-In one sentence: **the Community Edition lets one person push the platform to its limits; the Enterprise Edition lets an organization run it at scale** — team collaboration, SSO, RBAC, audit & compliance, industry data tools, persistent sandboxes, cloud storage, and white-labeling belong to EE. See [Edition Comparison](../editions/overview.md) and the [License mechanism](../editions/license.md) for the full matrix.
+| Durable runs | Every request is a persisted run executed in the background; resumable streams and mid-run steering; lease takeover and startup reconciliation; tool side effects record intent and receipt, and unknown outcomes are never replayed automatically | CE |
+| Personal Agent | One conversation, parallel background tasks, a permanent cloud computer, schedules and heartbeats, IM channels, action approvals, file-based memory with Dreaming, and connecting your own computer | CE (authorization governance and cross-device session management are EE) |
+| Autonomous loop | Goals survive across turns until done; a worker does the work, a read-only reviewer verifies it, and command-checkable requirements are verified by the driver; resumable after interruption | CE |
+| Chat and Plan Mode | SSE streaming, ReAct tool orchestration, deep thinking, Plan Mode, citations, context compaction checkpoints, chat forks | CE |
+| Sub-agents | Built-in explorer, worker and reviewer, user-created sub-agents, automatic routing or `@` mentions | CE (organization agent library is EE) |
+| MCP tools and plugins | Built-in web search, page fetching, knowledge retrieval, charts, automation, and skill and plugin management tools; an MCP marketplace, private MCP servers, a plugin marketplace and a browser automation plugin | CE (data warehouse and industry tools are EE) |
+| Agent Skills | SKILL.md plus scripts; built-in skills, a skill marketplace and personal skills | CE (skill review and organization governance are EE) |
+| Sandbox execution | OpenSandbox sessions with environment reuse and snapshot recovery, or a local subprocess sandbox; workflow jobs run as managed processes that can be taken over by lease | CE (CubeSandbox and team mounts are EE) |
+| Memory and personal evolution | L1 profile, L2 procedural memory (Milvus, optional), L3 relationship graph (Neo4j, optional); durable outbox writes; evolution proposals approved one by one | CE (memory auditing is EE) |
+| Knowledge base | Document chunking, hybrid vector and keyword retrieval, private knowledge bases, knowledge-base wiki | CE (public KB administration and Dify integration are EE) |
+| Domain ontology governance | Domain Pack import and versioned publishing, rule checks before tool execution, independent review of high-risk answers, human review of evolution drafts | CE |
+| Automation | Scheduled tasks in natural language, cron scheduling, retries on failure | CE |
+| Sites and application hosting | Build a site in chat and publish it in one click; separate application databases and MCP hosting | CE (site collaboration after project conversion is EE) |
+| Data canvas and artifacts | Univer spreadsheets, charts, reports, Office files, web pages and an artifact center | CE personal editing; real-time collaboration is EE |
+| Projects and My Space | Personal projects, folders, favourites and chat sharing; the desktop client can bind local project directories | CE (team folders and permission matrices are EE) |
+| Desktop client | Tauri 2 client with its own private runtime, supporting local-only, cloud-only and local + cloud modes | CE |
## Technology stack
-| Layer | Technology | Notes |
-|---|---|---|
-| Backend | FastAPI + Uvicorn (Python 3.11) | `src/backend/api/app.py`, unified response envelope |
-| Agent framework | AgentScope 2.0 (`agentscope==2.0.0`) | ReActAgent + tool registration, `core/llm/factory/build.py` |
-| Frontend | React 19 + Vite 7 + Zustand 5 + Ant Design 6 | `src/frontend/`, served by nginx with an `/api` reverse proxy |
-| Database | PostgreSQL 15 (production) / SQLite (local-debug fallback) | SQLAlchemy 2 + Alembic migrations |
-| Cache / sessions | Redis 7 | Session store, streaming follower (Redis Streams) |
-| Vector DB | Milvus 2.4 (`mem0` profile) | L2 vector memory and self-hosted KB retrieval |
-| Graph DB | Neo4j 5 Community (`mem0` profile, optional) | L3 knowledge-graph memory |
-| Sandbox | script_runner sidecar / Alibaba OpenSandbox / Tencent CubeSandbox | `core/sandbox/` provider protocol, switched via env |
-| Deployment | Docker Compose (profiles: `script_runner` / `opensandbox` / `mem0`) | Everything runs in containers; there is no local dev server |
-
-## Architecture at a glance
+| Layer | Technology |
+|---|---|
+| Agent runtime | AgentScope 2.0 (ReActAgent), Model Context Protocol |
+| Backend | Python 3.11, FastAPI, SQLAlchemy 2, Alembic |
+| Frontend | React 19, TypeScript, Vite, Zustand, Ant Design |
+| Desktop | Tauri 2, private CPython 3.11 runtime |
+| Data and state | PostgreSQL 15 or SQLite; Redis 7 or in-process state |
+| Storage | S3-compatible storage (cloud), local files (desktop and one-command install) |
+| Sandbox | OpenSandbox, local subprocess (CubeSandbox is EE) |
+| Optional memory | Milvus 2.4, Neo4j 5 Community, mem0 |
+| Deployment | Docker Compose, desktop installer, one-command installer, Nginx |
+
+## Architecture
```
- ┌──────────────────────────────────────────────┐
- Browser ──► Nginx ────►│ FastAPI backend (src/backend/api/app.py) │
- (frontend ctr, │ api/routes/v1/* · 50+ routers · envelope │
- /api proxy) └───────────────────┬──────────────────────────┘
- │
- ┌──────────────────────┼─────────────────────────┐
- ▼ ▼ ▼
- orchestration/workflow.py core/services/* core/auth/*
- (SSE streaming orchestration: (business service (local / mock /
- text / tool_call / layer) remote + SSO)
- tool_result / meta / done)
- │
- ┌─────────────┼───────────────────┬─────────────────────┐
- ▼ ▼ ▼ ▼
- core/llm/ orchestration/ orchestration/ core/memory/ (svc)
- factory/build strategy.py citations.py + memory_integration
- (AgentScope (routing (citation parsing) (mem0 retrieve/save)
- 2.0 ReAct) strategy) │
- │ ▼
- ├──► core/llm/mcp_manager ──► mcp container Milvus / Neo4j
- │ (9 MCP servers, ports 9100–9104, 9106–9108, and 9112) (mem0 profile)
- ├──► core/sandbox/* ──► script-runner / OpenSandbox / CubeSandbox
- └──► PostgreSQL · Redis · storage (local / S3 / OSS)
+ Entry points: web · desktop client · IM channels · API · schedules · heartbeats
+ │
+ ▼
+ FastAPI (api/) auth · edition routing · messages persisted before the response
+ │
+ ▼
+ Run layer (core/runs · core/chat/runtime)
+ persisted runs · database leases · startup reconciliation · resumable streams · steering · tool effect receipts
+ │
+ ▼
+ Agent execution (core/agent_runtime · core/llm)
+ ReActAgent · per-step context assembly and compaction · memory / ontology / evolution integration
+ Outer orchestration (orchestration/): Plan Mode · autonomous loop · automation
+ │
+ ┌────────────────┼──────────────────┐
+ ▼ ▼ ▼
+ MCP · skills · plugins sandbox · jobs knowledge · memory
+ │ │ │
+ └────────────────┴──────────────────┘
+ ▼
+ PostgreSQL / SQLite · Redis · S3 / local storage · Milvus · Neo4j
```
-Main request path: browser → nginx inside the frontend container (`/api` proxy) → FastAPI → `orchestration/workflow.py` orchestrates streaming → `core/llm/factory/build.py` builds the ReActAgent → MCP tools / sandbox / memory → SSE events (`text` / `tool_call` / `tool_result` / `meta` / `done`) stream back to the frontend.
+Personal Agent (`personal_agent/`) reuses the same runs, leases and sandboxes, and adds identity,
+a task queue, scheduling, channels, device links and workspace files. See the
+[architecture overview](../architecture/overview.md) for the full request lifecycle.
## Next steps
-- [Quick Start in 10 minutes](quick-start.md)
-- [Full Docker Compose deployment](../deployment/docker-compose.md)
-- [Environment variable reference](../deployment/environment-variables.md)
-- [Architecture overview](../architecture/overview.md)
+- [Quick start](quick-start.md)
+- [Personal Agent](../modules/personal-agent.md)
+- [Chat and orchestration](../modules/chat.md)
+- [Autonomous loop](../modules/autonomous-loop.md)
+- [Deployment guide](../deployment/README.md)
-## Related source
+## Source map
-| Feature | Files |
+| Area | Path |
|---|---|
-| FastAPI app & router registration | `src/backend/api/app.py`, `src/backend/api/routes/v1/` |
-| Streaming orchestration (SSE) | `src/backend/orchestration/workflow.py`, `orchestration/streaming.py` |
+| FastAPI app and routes | `src/backend/api/app.py`, `src/backend/api/routes/v1/` |
+| Run lifecycle, leases and recovery | `src/backend/core/runs/` |
+| Chat worker, stream resume and steering | `src/backend/core/chat/runtime/` |
+| Agent execution entry | `src/backend/core/agent_runtime/` (`runner.astream_chat_workflow`) |
| Agent construction | `src/backend/core/llm/factory/build.py` |
-| MCP servers & port mapping | `src/backend/extensions/mcp_servers/`, `src/backend/extensions/mcp_servers/_ports.py`, `src/backend/core/config/mcp_config.py` |
-| Capability catalog | `src/backend/core/config/catalog.json`, `core/config/catalog.py` |
-| Sandbox providers | `src/backend/core/sandbox/`, `src/backend/core/config/settings.py::SandboxSettings` |
-| Memory system | `src/backend/core/memory/` (service.py / pipeline.py), `src/backend/orchestration/memory_integration.py` |
-| Automation scheduler | `src/backend/orchestration/schedulers/automation_scheduler.py` |
-| Frontend entries (App / Admin / Config) | `src/frontend/src/main.tsx`, `App.tsx`, `AdminApp.tsx`, `ConfigApp.tsx` |
-| Edition & license settings | `src/backend/core/config/settings.py::EditionSettings / LicenseSettings` |
+| Harness contracts and execution ledger | `src/backend/core/harness/` |
+| Workflow background jobs | `src/backend/core/jobs/` |
+| Plan Mode, autonomous loop, automation | `src/backend/orchestration/plans/`, `orchestration/loops/`, `orchestration/automations/` |
+| Personal Agent | `src/backend/personal_agent/` |
+| Memory and evolution | `src/backend/core/memory/`, `src/backend/core/evolution/` |
+| Domain ontology | `src/backend/core/ontology/` |
+| Sandbox | `src/backend/core/sandbox/` |
+| Built-in MCP, skills, plugins and agents | `src/backend/extensions/` |
+| Frontend entry | `src/frontend/src/main.tsx`, `App.tsx` |
+| Desktop client | `desktop/src-tauri/` |
diff --git a/document/en/getting-started/quick-start.md b/document/en/getting-started/quick-start.md
index c3fea68f..a6e25a9a 100644
--- a/document/en/getting-started/quick-start.md
+++ b/document/en/getting-started/quick-start.md
@@ -1,22 +1,91 @@
# Quick start
-> Last updated: July 21, 2026 |
+> Last updated: October 23, 2026 |
> [简体中文](../../zh-CN/getting-started/quick-start.md)
-Choose the one-command installer for a personal trial or Docker Compose for a
-server-oriented, service-isolated deployment. Both methods require access to
-an OpenAI-compatible API or local model.
+There are three ways to get started. Choose HugAgentOS Cloud when agents should keep running in the
+cloud around the clock, the HugAgentOS Local desktop client to work offline on your own computer, or
+the one-command installer for development and quick trials. Every option needs access to an
+OpenAI-compatible API or a local model.
## Choose a deployment method
-The two deployment methods use different runtime and persistence models.
-
| Method | Best for | Runtime |
|---|---|---|
-| One-command installer | Personal trials and development | SQLite, in-process state, local subprocess sandbox |
-| Docker Compose | Long-running servers and service isolation | PostgreSQL, Redis, container sandbox, persistent volumes |
+| HugAgentOS Cloud (Docker Compose) | 24/7 operation, Personal Agent, IM channels, access from any device | PostgreSQL, Redis, OpenSandbox, persistent volumes |
+| HugAgentOS Local (desktop client) | Offline use, sensitive data, local projects | Bundled private runtime, local SQLite and files |
+| One-command installer | Trials and development | SQLite, in-process state, local subprocess sandbox |
+
+**The initial account and password are both `admin` and must be changed on first sign-in; the
+Community Edition has no self-registration.**
+
+## Option 1: HugAgentOS Cloud (Docker Compose)
+
+Install Git, Docker Engine or Docker Desktop, and Docker Compose v2, then run:
+
+```bash
+git clone https://github.com/ZJU-REAL/HugAgentOS.git
+cd HugAgentOS
+cp .env.example .env
+mkdir -p data/storage
+docker compose build backend
+docker compose up -d --build
+```
+
+Open [http://localhost:3002](http://localhost:3002). Sign in with `admin` /
+`admin`, change the password, then open **Settings → System Administration →
+Model Services** to connect a model. Use `docker compose ps` to inspect the
+services and `docker compose down` to stop them without deleting data.
+
+Read the [Docker Compose deployment guide](../deployment/docker-compose.md) for
+profiles, persistence, production configuration, and rebuild workflows.
+
+### Enable Personal Agent (optional)
+
+Personal Agent is off by default. It uses a dedicated OpenSandbox controller to keep a permanent
+cloud computer for each user.
+
+1. Build the computer image:
+
+ ```bash
+ docker build -f docker/personal-agent/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .
+ ```
+
+2. Set these values in `.env`:
+
+ | Variable | Value |
+ |---|---|
+ | `PERSONAL_AGENT_ENABLED` | `true` |
+ | `HOST_STORAGE_PATH` | An absolute path on the host |
+ | `PERSONAL_AGENT_OPENSANDBOX_API_KEY` | A separate random secret of at least 32 characters, for example the output of `openssl rand -hex 32` |
+
+3. Start with the additional Compose file:
+
+ ```bash
+ docker compose -f docker-compose.yml -f docker/personal-agent/compose.personal-agent.yml --profile personal-agent up -d
+ ```
+
+Then click **Personal Agent** under **New chat**. See [Personal Agent](../modules/personal-agent.md)
+for database migrations, image upgrades and channel binding. Never commit the secret to Git.
-## Option 1: one-command installer
+## Option 2: HugAgentOS Local (desktop client)
+
+1. Download the desktop installer for your platform (Windows x86_64, macOS Apple Silicon / Intel,
+ Linux x86_64) from [Releases](https://github.com/ZJU-REAL/HugAgentOS/releases).
+2. Choose a mode on first launch:
+ - **Local only**: the service, data and execution stay on this computer and can run fully
+ offline;
+ - **Cloud only**: connect to a deployed HugAgentOS Cloud;
+ - **Local + cloud**: use your cloud account; cloud projects stay in the cloud and local
+ projects run on this machine, which also lets Personal Agent connect to this computer.
+3. In local mode, the client verifies and unpacks its bundled runtime, usually within tens of
+ seconds, with no Python, Docker or database to install.
+
+See [Desktop local mode](../deployment/desktop-local-mode.md) for installation, upgrade rollback
+and data directories, and [Local project workspace](../deployment/local-project-workspace.md) for
+how execution works in a bound local project.
+
+## Option 3: one-command installer
Use this method on Linux, macOS, or Windows through WSL2. Install the following
tools before you start.
@@ -56,7 +125,7 @@ After setup, HugAgentOS starts automatically and opens
Run the installed command whenever you want to start the application again:
```bash
-~/.hugagent/venv/bin/hugagent
+~/.hugagent/venv/bin/hugagent-server
```
You can add the command to your shell path:
@@ -65,36 +134,20 @@ You can add the command to your shell path:
export PATH="$HOME/.hugagent/venv/bin:$PATH"
```
-## Option 2: Docker Compose
-
-Use this method when you need durable service volumes and container isolation.
-Install Git, Docker Engine or Docker Desktop, and Docker Compose v2, then run:
-
-```bash
-git clone https://github.com/ZJU-REAL/HugAgentOS.git
-cd HugAgentOS
-cp .env.example .env
-mkdir -p data/storage
-docker compose up -d --build
-```
-
-Open [http://localhost:3002](http://localhost:3002). Sign in with `admin` /
-`admin`, change the password, then open **Settings → System Administration →
-Model Services** to connect a model. Use `docker compose ps` to inspect the
-services and `docker compose down` to stop them without deleting data.
-
-Read the [Docker Compose deployment guide](../deployment/docker-compose.md) for
-profiles, persistence, production configuration, and rebuild workflows.
+The one-command installer has no cross-process lease takeover and is not meant for 24/7 operation.
## Next steps
Continue with these guides after your first successful login:
-- Read the [complete no-Docker installation guide](../deployment/quick-install.md)
- for installer options, capability boundaries, and troubleshooting.
+- Read the [introduction](introduction.md) for the durable agent, runtime forms and feature overview.
- Configure [model providers](../modules/model-providers.md).
-- Build a governed workflow with the
- [domain ontology quickstart](domain-ontology-quickstart.md).
+- Try [Personal Agent](../modules/personal-agent.md) and the
+ [autonomous loop](../modules/autonomous-loop.md).
- Explore [MCP tools](../modules/mcp-tools.md),
[Agent Skills](../modules/agent-skills.md), and
[private knowledge bases](../modules/knowledge-base.md).
+- Build a governed workflow with the
+ [domain ontology quickstart](domain-ontology-quickstart.md).
+- Read the [complete no-Docker installation guide](../deployment/quick-install.md)
+ for installer options, capability boundaries, and troubleshooting.
diff --git a/document/en/modules/admin-console.md b/document/en/modules/admin-console.md
index ae7a6d27..141faa56 100644
--- a/document/en/modules/admin-console.md
+++ b/document/en/modules/admin-console.md
@@ -53,11 +53,11 @@ Page Configuration also owns the homepage's first-screen content. Administrators
`/v1/admin/skills` (`api/routes/v1/admin_skills.py`, `ADMIN_TOKEN`): full skill lifecycle — CRUD, zip upload, enable/disable, ordering, icons, dependency scan and editing, per-file read/write/delete inside a skill, forking built-in skills, import/export. Skills are stored in the `admin_skills` table; see [Agent Skills](agent-skills.md).
-`/v1/admin/skill-drafts` (`api/routes/v1/admin_skill_drafts.py`, `ADMIN_TOKEN`): review of auto-distilled skill drafts — list / count / detail, edit-then-approve, reject, delete, plus a manual trigger for the daily distillation scan.
+`/v1/admin/skill-drafts` (`features/distillation/api/admin_skill_drafts.py`, `ADMIN_TOKEN`): review of auto-distilled skill drafts — list / count / detail, edit-then-approve, reject, delete, plus a manual trigger for the daily distillation scan.
### Marketplace review (admin_marketplace) (Enterprise Edition: content_admin)
-`/v1/admin/marketplace` (`api/routes/v1/admin_marketplace.py`, `ADMIN_TOKEN`) does two things:
+`/v1/admin/marketplace` (`features/marketplace/api/admin_marketplace.py`, `ADMIN_TOKEN`) does two things:
1. **Install marketplace skills globally**: browse the marketplace listing (flagging "already installed globally"); installed skills have an empty owner, are available to everyone, and remain editable under Skill Management.
2. **Review user listing submissions**: submission list (filterable by status), detail (with SKILL.md preview), approve (publish, installable by all), reject / unpublish.
@@ -91,14 +91,14 @@ The sub-agent status switch uses `PUT /v1/admin/agents/{agent_id}/toggle`. While
### Knowledge base management (admin_kb) (Enterprise Edition: content_admin)
-`/v1/admin/kb` (`api/routes/v1/admin_kb.py`, `ADMIN_TOKEN`): management of self-hosted **public knowledge bases** (`KBSpace.visibility == "public"`, owned by the system owner), mirroring the user-side `kb.py` — KB CRUD, AI-generated descriptions, document upload / listing / original-file preview (Office→PDF) / deletion / reindexing, chunk preview and per-chunk editing (content / tags / questions). Public KBs are visible to all users in the capability catalog and searchable. In Dify mode, Dify datasets are shown read-only and writes return 409. See [Knowledge Base](knowledge-base.md).
+`/v1/admin/kb` (`edition_ee/routes/admin_kb.py`, `ADMIN_TOKEN`): management of self-hosted **public knowledge bases** (`KBSpace.visibility == "public"`, owned by the system owner), mirroring the user-side `kb.py` — KB CRUD, AI-generated descriptions, document upload / listing / original-file preview (Office→PDF) / deletion / reindexing, chunk preview and per-chunk editing (content / tags / questions). Public KBs are visible to all users in the capability catalog and searchable. In Dify mode, Dify datasets are shown read-only and writes return 409. See [Knowledge Base](knowledge-base.md).
### Sandbox management (admin_sandbox + config_security/sandbox)
Two complementary panels:
-- `/v1/admin/sandbox` (`api/routes/v1/admin_sandbox.py`, `ADMIN_TOKEN`, **Enterprise Edition: content_admin**): aggregated skill-dependency manifest preview, sandbox image rebuild trigger + container hot-swap, rebuild history and log queries.
-- `/v1/config/security/sandbox/*` (`api/routes/v1/config_security.py`, `CONFIG_TOKEN`, **Enterprise Edition: system_config**): read-only security view — sandbox overview, running instances list / detail, snapshots, rebuild history, redacted configuration. Capabilities a provider cannot support (e.g. ScriptRunner cannot enumerate instances) return `code=42210`, which the frontend renders as disabled.
+- `/v1/admin/sandbox` (`features/sandbox_ops/api/admin_sandbox.py`, `ADMIN_TOKEN`, **Enterprise Edition: content_admin**): aggregated skill-dependency manifest preview, sandbox image rebuild trigger + container hot-swap, rebuild history and log queries.
+- `/v1/config/security/sandbox/*` (`features/security_console/api/config_security.py`, `CONFIG_TOKEN`, **Enterprise Edition: system_config**): read-only security view — sandbox overview, running instances list / detail, snapshots, rebuild history, redacted configuration. Capabilities a provider cannot support (e.g. ScriptRunner cannot enumerate instances) return `code=42210`, which the frontend renders as disabled.
See [Sandbox](sandbox.md).
@@ -166,13 +166,13 @@ Aligned with chapter 4 of the productization plan and the route registry:
| Administrative credential dependencies | `src/backend/api/deps.py` |
| License features | `src/backend/edition_ee/licensing/features.py`, `src/backend/edition_ee/licensing/deps.py` |
| Skills / drafts / marketplace | `src/backend/api/routes/v1/admin_skills.py`, `admin_skill_drafts.py`, `admin_marketplace.py` |
-| Feedback and GitHub publishing | `src/backend/edition_ee/routes/feedbacks.py`, `admin_feedbacks.py`, `services/feedback_service.py` |
+| Feedback and GitHub publishing | `src/backend/edition_ee/routes/feedbacks.py`, `admin_feedbacks.py`, `edition_ee/services/feedback_service.py` |
| Prompts / MCP / sub-agents | `src/backend/api/routes/v1/admin_prompts.py`, `admin_mcp_servers.py`, `admin_agents.py` |
-| Knowledge base / sandbox | `src/backend/edition_ee/routes/admin_kb.py`, `src/backend/api/routes/v1/admin_sandbox.py` |
+| Knowledge base / sandbox | `src/backend/edition_ee/routes/admin_kb.py`, `src/backend/features/sandbox_ops/api/admin_sandbox.py` |
| Billing / usage / logs / chat review | `src/backend/api/routes/v1/admin_billing.py`, `admin_usage_logs.py`, `admin_logs.py`, `admin_chat_history.py` |
| Content management | `src/backend/api/routes/v1/content.py` |
| Users / teams / invites / license (EE) | `src/backend/edition_ee/routes/config_users.py`, `config_teams.py`, `config_invites.py`, `config_license.py` |
-| Security | `src/backend/api/routes/v1/config_security.py` |
+| Security | `src/backend/features/security_console/api/config_security.py` |
| Service configs | `src/backend/api/routes/v1/service_configs.py` |
Further reading: [Authentication & Permissions](auth.md) · [Prompt System](prompts.md) · [Editions & Licensing](../editions/overview.md)
diff --git a/document/en/modules/agent-skills.md b/document/en/modules/agent-skills.md
index 18846fd9..8af22ed4 100644
--- a/document/en/modules/agent-skills.md
+++ b/document/en/modules/agent-skills.md
@@ -29,11 +29,11 @@ Download integrity, path containment and account ownership checks remain enforce
└── _scripts.json # optional: script whitelist (auto-detected by extension when absent)
```
-Parsing and data structures live in `core/agent_skills/registry.py`: `AgentSkillMetadata` (lightweight, for listings) and `AgentSkillSpec` (full instructions, for execution). When `_scripts.json` is missing, `loader._auto_detect_scripts` builds the whitelist from file extensions automatically.
+Parsing and data structures live in `core/capabilities/skills/registry.py`: `AgentSkillMetadata` (lightweight, for listings) and `AgentSkillSpec` (full instructions, for execution). When `_scripts.json` is missing, `loader._auto_detect_scripts` builds the whitelist from file extensions automatically.
## Multi-source loading and priorities
-`core/agent_skills/loader.py::MultiSourceSkillLoader` loads skills from several sources through a backend abstraction (`backends/`: filesystem / database / composite); on ID conflicts the higher-priority source wins (`core/agent_skills/config.py`):
+`core/capabilities/skills/loader.py::MultiSourceSkillLoader` loads skills from several sources through a backend abstraction (`backends/`: filesystem / database / composite); on ID conflicts the higher-priority source wins (`core/capabilities/skills/config.py`):
| Source | Directory | Priority | Notes |
|---|---|---|---|
@@ -149,11 +149,11 @@ Key points:
- **Scope**: main-chat assembly and sub-agents. A sub-agent's bound plugins are deferred the same way, but activation lives only within that run and is never persisted (sub-agent runs are short-lived and isolated; writing under the parent chat's key would leak the activation into the main agent's assembly). Plugins pinned by a restricted chat mode stay eagerly loaded (a deliberate admin narrowing); plugins with stdio-transport MCP components are not deferred.
- **Kill switch**: set `PLUGIN_PROGRESSIVE_LOADING=false` to revert to eager assembly.
-Implementation: `core/plugins/runtime/__init__.py` (deferral resolution / directory rendering / activation tool) plus the assembly hook in `core/llm/factory/build.py`.
+Implementation: `core/capabilities/plugins/runtime/__init__.py` (deferral resolution / directory rendering / activation tool) plus the assembly hook in `core/llm/factory/build.py`.
## The Skill Marketplace
-The marketplace is an installable skill library with two sources — curated presets and community submissions — implemented in `core/services/marketplace_service.py`.
+The marketplace is an installable skill library with two sources — curated presets and community submissions — implemented in `features/marketplace/`.
### Browsing and installing
@@ -205,7 +205,7 @@ admin GET/PUT /v1/admin/marketplace/skills/{slug}/visibility (skill ma
GET /v1/admin/visibility/principals ← brief user/team/role lists (picker data source)
```
-Storage: `marketplace_listing_states.visibility` (missing row = public) plus the `marketplace_visibility_grants` whitelist table; user-side resolution is centralized in `core/auth/marketplace_visibility.py` (roles include both direct assignments and team-default roles inherited via team membership).
+Storage: `marketplace_listing_states.visibility` (missing row = public) plus the `marketplace_visibility_grants` whitelist table; user-side resolution is centralized in `edition_ee/auth/marketplace_visibility.py` (roles include both direct assignments and team-default roles inherited via team membership).
## Admin skill management
@@ -214,7 +214,7 @@ The `/admin` console's skill management maps to `api/routes/v1/admin_skills.py`
- **Create/edit**: hand-written SKILL.md (`POST /`, `PUT /{id}`) or **zip upload** of an entire package (`POST /upload`, auto-detects directory prefixes, base64-encodes binaries into the DB, 200 MB cap);
- **File-level management**: `GET/PUT/DELETE /{id}/files/{filename}` for inline editing of auxiliary files;
- **Operations**: toggle (`/{id}/toggle`), ordering, icons, fork, bulk export/import (cross-environment migration);
-- **Dependency management**: `POST /{id}/rescan-deps` statically scans scripts for pip/apt dependencies via `core/agent_skills/deps_detector.py`, with `PUT /{id}/dependencies` for manual correction; the aggregated manifest feeds the sandbox image rebuild (see the admin dependency rebuild in the [sandbox module](sandbox.md)).
+- **Dependency management**: `POST /{id}/rescan-deps` statically scans scripts for pip/apt dependencies via `core/capabilities/skills/deps/detector.py`, with `PUT /{id}/dependencies` for manual correction; the aggregated manifest feeds the sandbox image rebuild (see the admin dependency rebuild in the [sandbox module](sandbox.md)).
## Skill distillation (Enterprise EE)
@@ -233,7 +233,7 @@ daily cron (default 02:30, DISTILL_CRON_EXPRESSION)
approve → promoted to AdminSkill; reject / delete; manual trigger-daily-scan available
```
-Thresholds, keywords and budgets are all overridable via `DISTILL_*` environment variables (`core/config/distillation.py`). The distillation pipeline (`skill_distiller.py`, the scheduler) is **Enterprise EE**; the CE tree strips it, and the draft-review console only appears in the Enterprise Edition.
+Thresholds, keywords and budgets are all overridable via `DISTILL_*` environment variables (`features/distillation/config.py`). The distillation pipeline (`skill_distiller.py`, the scheduler) is **Enterprise EE**; the CE tree strips it, and the draft-review console only appears in the Enterprise Edition.
## User self-service skills (capability center)
@@ -281,22 +281,22 @@ only submission, never conversation or continued local editing.
| Path | Description |
|---|---|
-| `src/backend/core/agent_skills/registry.py` | SKILL.md parsing, metadata / full-spec dataclasses |
-| `src/backend/core/agent_skills/loader.py` | Multi-source loading, DB-skill materialization, sandbox path repointing |
-| `src/backend/core/agent_skills/config.py` | Source priorities, unified sandbox skills dir, built-in sync |
-| `src/backend/core/agent_skills/selector.py` | LLM-based skill selection per user intent (progressive disclosure) |
-| `src/backend/core/agent_skills/deps_detector.py` | Static pip/apt dependency detection for scripts |
-| `src/backend/core/agent_skills/backends/` | filesystem / database / composite loading backends |
+| `src/backend/core/capabilities/skills/registry.py` | SKILL.md parsing, metadata / full-spec dataclasses |
+| `src/backend/core/capabilities/skills/loader.py` | Multi-source loading, DB-skill materialization, sandbox path repointing |
+| `src/backend/core/capabilities/skills/config.py` | Source priorities, unified sandbox skills dir, built-in sync |
+| `src/backend/core/llm/skill_selection.py` | LLM-based skill selection per user intent (progressive disclosure) |
+| `src/backend/core/capabilities/skills/deps/detector.py` | Static pip/apt dependency detection for scripts |
+| `src/backend/core/capabilities/skills/backends/` | filesystem / database / composite loading backends |
| `src/backend/core/llm/tools/skill_tool.py` | Restricted view_text_file + {baseDir} substitution + Runtime Hint |
| `src/backend/extensions/skills/default/` | The 12 built-in skills (incl. the 10 OfficeCLI ones) |
| `src/backend/extensions/skills/marketplace/` | The 45 installable marketplace packages |
-| `src/backend/core/services/marketplace_service.py` | Marketplace listing / install / secret injection / submission review |
-| `src/backend/api/routes/v1/marketplace.py` | User-side marketplace API (browse/install/submit/withdraw) |
-| `src/backend/api/routes/v1/admin_marketplace.py` | Admin marketplace API (global install / submission review) |
+| `src/backend/features/marketplace/` | Marketplace listing / install / secret injection / submission review |
+| `src/backend/features/marketplace/api/marketplace.py` | User-side marketplace API (browse/install/submit/withdraw) |
+| `src/backend/features/marketplace/api/admin_marketplace.py` | Admin marketplace API (global install / submission review) |
| `src/backend/api/routes/v1/admin_skills.py` | Admin skill CRUD / zip / dependency management |
-| `src/backend/api/routes/v1/admin_skill_drafts.py` | Distillation draft review (EE) |
-| `src/backend/core/llm/skill_distiller.py` | Skill distillation LLM pipeline (EE) |
-| `src/backend/orchestration/schedulers/distillation_cron_scheduler.py` | Daily distillation scheduler (EE) |
+| `src/backend/features/distillation/api/admin_skill_drafts.py` | Distillation draft review (EE) |
+| `src/backend/features/distillation/` | Skill distillation LLM pipeline (EE) |
+| `src/backend/orchestration/workers/distillation_cron_scheduler.py` | Daily distillation scheduler (EE) |
| `src/backend/api/routes/v1/me_capabilities.py` | User self-service skill / private MCP API |
Related docs: [Sandbox execution](sandbox.md) · [MCP tool system](mcp-tools.md) · [Capability catalog](catalog.md) · [Admin console](admin-console.md) · [Editions & licensing](../editions/overview.md)
diff --git a/document/en/modules/application-hosting.md b/document/en/modules/application-hosting.md
index 2173c621..be4f44a4 100644
--- a/document/en/modules/application-hosting.md
+++ b/document/en/modules/application-hosting.md
@@ -6,11 +6,11 @@ Static sites do not create databases. Persistent forms create typed application
Set `APPLICATION_DATABASE_URL` to a separate database and run `python -m core.services.application_hosting_setup` once. Cloud installations without this setting show hosting as unavailable; local mode may use SQLite. Production uses PostgreSQL.
-The optional `docker-compose.application-hosting.yml` adds PostgreSQL, a persistent volume and an initialization job. Set different URL-safe `APPLICATION_DB_PASSWORD` (bootstrap) and `APPLICATION_OWNER_DB_PASSWORD` (runtime), or encode connection strings correctly. The initialization job uses the same Dockerfile build target as the backend and mounts current backend sources read-only. Build the backend from these sources and rebuild the frontend for the nginx `/applications-mcp/` route. Follow repository approval rules before deployment. End-to-end tests use separate databases and platform mock authentication for local testing only.
+The optional `docker/application-hosting/compose.application-hosting.yml` adds PostgreSQL, a persistent volume and an initialization job. Set different URL-safe `APPLICATION_DB_PASSWORD` (bootstrap) and `APPLICATION_OWNER_DB_PASSWORD` (runtime), or encode connection strings correctly. The initialization job uses the same Dockerfile build target as the backend and mounts current backend sources read-only. Build the backend from these sources and rebuild the frontend for the nginx `/applications-mcp/` route. Follow repository approval rules before deployment. End-to-end tests use separate databases and platform mock authentication for local testing only.
```bash
-docker compose -f docker-compose.yml -f docker-compose.application-hosting.yml config --quiet
-docker compose -f docker-compose.yml -f docker-compose.application-hosting.yml up -d
+docker compose -f docker-compose.yml -f docker/application-hosting/compose.application-hosting.yml config --quiet
+docker compose -f docker-compose.yml -f docker/application-hosting/compose.application-hosting.yml up -d
```
## Access and data
@@ -43,27 +43,7 @@ Schema validation, relational table construction, registry transactions, data op
## Limits
-Implemented: typed SQL, bounded reads/writes, atomic batches, idempotency, optimistic updates, application isolation, anonymous insert-only forms and declarative MCP hosting. Not implemented: arbitrary-code MCP containers, OAuth authorization server, foreign-key relationship designer, backup/restore console and comprehensive usage billing. The real chat driver is `src/backend/tests/application_hosting_e2e.py`; `application_hosting_e2e_setup.py` prepares its isolated environment. It exercises the production chat API, model, tools, sandbox and publishing. Acceptance status comes from the actual run report.
-
-## Reproduce local end-to-end acceptance
-
-Use a local Docker environment with a real model, industry source and OpenSandbox configured. Build the current frontend first. These drivers use production APIs and actual tools, with no file or publication adapters. Test services bind loopback ports and use separate platform_e2e_verified / applications_e2e_verified databases and storage.
-
-```bash
-npm --prefix src/frontend run build
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_e2e_setup.py
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_e2e.py
-```
-
-Submit test data at `http://127.0.0.1:18533/site/e2e-form-1007/` using email `browser-e2e@example.test`. Then run:
-
-```bash
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_lifecycle_e2e.py
-```
-
-The lifecycle driver restarts only the isolated test services and rotates, rolls back, revokes and republishes the test MCP credential. Credentials stay in `/tmp/application-hosting-full-e2e-credentials.json` (0600), outside pages and acceptance reports. Existing batches are not automatically reset; use a clean isolated database batch for a full chat rerun. This workflow does not upgrade the primary installation or deploy remotely.
-
-For a fresh chat rerun, stop and remove only `hugagent-application-e2e-backend` and `hugagent-application-e2e-mcp`, then run setup with `APPLICATION_E2E_BATCH=run2`. Previous batch databases remain intact and drivers keep the same loopback URLs. Do not remove primary containers or databases.
+Implemented: typed SQL, bounded reads/writes, atomic batches, idempotency, optimistic updates, application isolation, anonymous insert-only forms and declarative MCP hosting. Not implemented: arbitrary-code MCP containers, OAuth authorization server, foreign-key relationship designer, backup/restore console and comprehensive usage billing.
## Desktop integration
@@ -135,9 +115,9 @@ and project files so subsequent upgrades reuse bindings; do not delete applicati
Management and rollback publication reject an existing unpublished project draft instead of overwriting it. Project publication waits for file-tool registration and refreshes the mounted source to its new version before reporting synchronization complete.
-## External review fixes and upgrades
+## Deployment requirements and data safety
-Application hosting remains optional. Set different APPLICATION_DB_PASSWORD and APPLICATION_OWNER_DB_PASSWORD values. Use both docker-compose.yml and docker-compose.application-hosting.yml.
+Application hosting remains optional. Set different APPLICATION_DB_PASSWORD and APPLICATION_OWNER_DB_PASSWORD values. Use both docker-compose.yml and docker/application-hosting/compose.application-hosting.yml.
Run application-database-init for new deployments and existing volumes. It creates recovery history and transfers application schema and table ownership to application_owner. It also grants access to existing application roles. Initialization failures must block backend startup.
diff --git a/document/en/modules/auth.md b/document/en/modules/auth.md
index 5689136e..08641a54 100644
--- a/document/en/modules/auth.md
+++ b/document/en/modules/auth.md
@@ -64,7 +64,7 @@ ontology validation.
`users_shadow.metadata`. Refreshing or signing in again doesn't repeat a
completed setup flow.
-The terminal-based `hugagent onboard` command already configures the account
+The terminal-based `hugagent-server onboard` command already configures the account
and model. After it finishes successfully, it writes the same completion
marker so the browser doesn't repeat the setup.
@@ -120,7 +120,7 @@ User-facing endpoints:
### Interface layer (CE/EE split seam)
-`core/auth/permissions_iface.py` is the **single import point** for permission symbols: mainline code (deps / files / chats / projects / kb, etc.) imports only from here, never from the concrete implementations. In the EE main repo it purely re-exports three real implementations; in the CE-derived tree the whole file is replaced by a single-tenant stub and the three implementation files do not physically exist:
+`core/auth/personal_permissions_iface.py` is the **single import point** for permission symbols: mainline code (deps / files / chats / projects / kb, etc.) imports only from here, never from the concrete implementations. In the EE main repo it purely re-exports three real implementations; in the CE-derived tree the whole file is replaced by a single-tenant stub and the three implementation files do not physically exist:
| Implementation file | Responsibility |
|---|---|
@@ -185,7 +185,7 @@ The actual gating, verified against each route file's dependencies:
| `PATCH /v1/me/api-keys/{key_id}` | Enable / disable |
| `DELETE /v1/me/api-keys/{key_id}` | Revoke |
-Keys look like `sk-jx-...` and can be used as a Bearer token to call business APIs **in every AUTH_MODE** (external programmatic callers usually have no cookies). Validation (enabled / not revoked / not expired / user flag still on) is centralized in `core/services/api_key_service.py::resolve_api_key`.
+Keys look like `sk-jx-...` and can be used as a Bearer token to call business APIs **in every AUTH_MODE** (external programmatic callers usually have no cookies). Validation (enabled / not revoked / not expired / user flag still on) is centralized in `core/auth/api_keys.py::resolve_api_key`.
Disabling or revoking an API key rejects new requests and cancels its admitted chat tasks that are pending, running, or awaiting attention. Cancellation stops background execution and terminates the SSE stream; completed external tool effects cannot be undone. Re-enabling the key does not resume cancelled tasks. Call duration stays fixed after execution stops, including the needs-attention state.
@@ -210,13 +210,13 @@ Key authentication events all land in the audit table (`audit_logs`): login succ
| SSO ticket exchange | `src/backend/edition_ee/auth/sso.py`, `src/backend/edition_ee/routes/auth.py` |
| Mock SSO / local login & registration page | `src/backend/api/routes/v1/mock_sso.py`, `src/backend/core/auth/mock_ticket_store.py` |
| Password hashing | `src/backend/core/auth/password.py` |
-| Permission interface layer (CE/EE seam) | `src/backend/core/auth/permissions_iface.py` |
+| Permission interface layer (CE/EE seam) | `src/backend/core/auth/personal_permissions_iface.py` |
| Team roles / file permissions (EE) | `src/backend/edition_ee/auth/roles.py`, `src/backend/edition_ee/auth/team_permissions.py` |
| Project / chat-share permissions (EE) | `src/backend/edition_ee/auth/project_permissions.py`, `src/backend/edition_ee/auth/chat_share_permissions.py` |
| Administrative credential dependencies | `src/backend/api/deps.py` |
| Profile / preferences | `src/backend/api/routes/v1/users.py` |
| Per-user permission flags | `src/backend/edition_ee/routes/config_users.py` |
-| Personal API keys | `src/backend/api/routes/v1/api_keys.py`, `src/backend/core/services/api_key_service.py` |
+| Personal API keys | `src/backend/api/routes/v1/api_keys.py`, `src/backend/core/auth/api_keys.py` |
| Capability-center self-service (owner isolation) | `src/backend/api/routes/v1/me_capabilities.py` |
| Invite codes | `src/backend/edition_ee/auth/invite.py`, `src/backend/edition_ee/routes/config_invites.py` |
| Team management | `src/backend/edition_ee/routes/config_teams.py`, `src/backend/edition_ee/routes/me_teams.py` |
diff --git a/document/en/modules/automation.md b/document/en/modules/automation.md
index 4fb0380c..d2c375f6 100644
--- a/document/en/modules/automation.md
+++ b/document/en/modules/automation.md
@@ -6,8 +6,8 @@ HugAgentOS ships two built-in mechanisms for turning a single instruction into r
| Capability | One-liner | Backend entry |
|---|---|---|
-| Scheduled automation | Run a prompt or a plan automatically on a cron schedule (or once / manually) | `api/routes/v1/automations.py` + `orchestration/schedulers/automation_scheduler.py` |
-| Plan mode | AI first decomposes a complex task into structured steps, then executes them after confirmation | `api/routes/v1/plans.py` + `orchestration/subagents/plan_mode.py` |
+| Scheduled automation | Run a prompt or a plan automatically on a cron schedule (or once / manually) | `features/automation/api/automations.py` + `orchestration/automations/automation_scheduler.py` |
+| Plan mode | AI first decomposes a complex task into structured steps, then executes them after confirmation | `api/routes/v1/plans.py` + `orchestration/plans/` |
## Scheduled automation tasks
@@ -17,7 +17,7 @@ Local scheduled tasks belong to the signed-in cloud account. The account mapping
### Task model
-Tasks live in the `scheduled_tasks` table (`ScheduledTask` in `core/db/models/automation.py`). Key fields:
+Tasks live in the `scheduled_tasks` table (`ScheduledTask` in `edition_ee/db/models/automation.py`). Key fields:
| Field | Description |
|---|---|
@@ -40,7 +40,7 @@ Tasks live in the `scheduled_tasks` table (`ScheduledTask` in `core/db/models/au
### Scheduling mechanics (automation_scheduler.py)
-`orchestration/schedulers/automation_scheduler.py` is an asyncio polling scheduler started with the backend:
+`orchestration/automations/automation_scheduler.py` is an asyncio polling scheduler started with the backend:
- **Polling**: every 15 seconds (plus 0–5 s random jitter) it queries the DB for tasks whose `next_run_at` is due.
- **Distributed lock**: before firing, it acquires the Redis lock `jx:auto:lock:{task_id}` with continuous renewal throughout execution and token-checked release on completion so multiple instances never double-fire.
@@ -48,6 +48,7 @@ Tasks live in the `scheduled_tasks` table (`ScheduledTask` in `core/db/models/au
- **Execution duration**: there is no fixed total runtime limit for scheduled or manually triggered Prompt, Plan, or Loop tasks. Their conversations and successors are also exempt from the ordinary chat absolute-age cap.
- **Failure governance**: when consecutive failures reach `max_failures` (default 3), the task is automatically set to `disabled`.
- **Orphan recovery**: at startup and on every poll, a worker must acquire execution ownership before resuming observation of the linked durable conversation and saving its final outcome. Only orphan runs without a linked conversation are marked `failed`, with overdue schedules advanced and one-shot tasks finalized. Runtime never determines recovery. The lock renews every 15 seconds; its 60-second lease detects a disconnected worker and does not limit total runtime. Missed one-shot tasks also acquire ownership before firing.
+- **Execution lease shutdown**: shutdown signals stop, cancels renewal, waits at most 2 seconds, and always attempts token-checked deletion. Local ownership markers are removed even on cleanup errors. Renewal also stops when the execution Task finishes. Result delivery has a 60-second total wait budget; failures do not overwrite committed success and do not retry automatically. Running records carry an internal ownership token; completion requires both running status and a matching token. Recovery claims the record under its new token.
Each execution creates and immediately links a **real conversation** through the shared ChatRun executor. Prompt runs stream text, thinking and tools; plan runs stream step progress. Opening a running conversation or refreshing the page follows and replays the same event stream as ordinary chat. Users can stop execution from the conversation; prompt runs also accept additional instructions through the durable steering queue at the next safe model boundary. Stopping does not increase consecutive failures, and loss of execution ownership or scheduler shutdown cancels the child run. Final messages, artifacts, citations and delivery remain owned by the shared executor and scheduler outcome handler. Conversation titles use the task name without an automation prefix. Older executions started before live-stream support cannot replay events that were never recorded.
@@ -63,7 +64,7 @@ Opening the right-side Canvas in an execution conversation temporarily hides the
## Plan mode
-Plan mode decomposes a complex task into structured steps before executing them, in two phases (`orchestration/subagents/plan_mode.py`):
+Plan mode decomposes a complex task into structured steps before executing them, in two phases (`orchestration/plans/`):
1. **Generate (Phase 1)**: `POST /v1/plans/generate` (SSE streaming) — the AI analyzes the task description and produces a plan draft with a title, description, and step list; each step can declare `expected_tools` / `expected_skills` / `expected_agents`.
2. **Execute (Phase 2)**: `POST /v1/plans/{plan_id}/execute` (SSE streaming) — steps run sequentially, each with its own agent; step status, tool-call logs, and AI output are persisted incrementally.
@@ -98,14 +99,41 @@ Every morning an Industry morning brief execution conversation is created. The s
| Topic | Path |
|---|---|
-| Automation REST API | `src/backend/api/routes/v1/automations.py` |
-| Scheduler (polling / locks / recovery) | `src/backend/orchestration/schedulers/automation_scheduler.py` |
-| Automation service (cron computation / state machine) | `src/backend/core/services/automation_service.py` |
-| Task / run-record models | `src/backend/core/db/models/automation.py` |
+| Automation REST API | `src/backend/features/automation/api/automations.py` |
+| Scheduler (polling / locks / recovery) | `src/backend/orchestration/automations/automation_scheduler.py` |
+| Automation service (cron computation / state machine) | `src/backend/features/automation/service.py` |
+| Task / run-record models | `src/backend/features/automation/models.py` |
| Plan REST API | `src/backend/api/routes/v1/plans.py` |
-| Plan generation / execution orchestration | `src/backend/orchestration/subagents/plan_mode.py`, `src/backend/core/services/plan_service.py` |
+| Plan generation / execution orchestration | `src/backend/orchestration/plans/`, `src/backend/core/chat/plans.py` |
| Plan / step models | `src/backend/core/db/models/agent.py` |
-| Frontend automation UI | `src/frontend/src/components/lab/AutomationPanel.tsx` etc., `src/frontend/src/components/automation/RunTimelinePanel.tsx` |
+| Frontend automation UI | `src/frontend/src/components/automation/AutomationPanel.tsx` etc., `src/frontend/src/components/automation/RunTimelinePanel.tsx` |
| Frontend state | `src/frontend/src/stores/automationStore.ts`, `automationChatStore.ts` |
Further reading: [Chat System](chat.md) · [MCP Tools](mcp-tools.md) · [Canvas & Artifacts](canvas-artifacts.md)
+
+## Multi-process execution and recovery
+
+Scheduled and manual triggers share one loop, scanned every 3 seconds with at most
+8 observed executions. Trigger consumption, schedule advancement and insertion of
+a pending run commit in one transaction. Conditional database updates fence
+concurrent admission. A manual trigger for a busy task remains queued.
+
+Pending means accepted and awaiting execution. Scheduler shutdown or lease loss
+transfers observation without cancelling the accepted conversation. A successor
+observes the same durable execution and its handoffs. User cancellation still
+stops execution. Child linkage and recovery arguments commit with acceptance;
+recovery never restarts a command to obtain its result. Existing notification
+delivery semantics remain; this change does not promise exactly-once delivery
+to external channels.
+
+Apply FULL migration automationqueue01 or CE migration ce_0032 before rollout.
+Local SQLite uses the installed automation module's schema initialization.
+Downgrade refuses pending records rather than discarding accepted work. Stop old
+schedulers, migrate, then start the new version; do not mix scheduling protocols.
+
+Plan/Loop persist all initial arguments with child acceptance. After a crash before
+worker registration, the successor dispatches only pending children. It never
+replays an already-running child from the beginning. Drain old in-flight work
+before upgrading; missing legacy arguments are not reconstructed by a compatibility path.
+
+Task delivery uses delivery_targets exclusively. The upgrade migrates flat channel_id/conversation_id fields into destination lists, removes the old fields, and preserves explicit destinations and unrelated metadata. Request handling no longer reads the old format.
diff --git a/document/en/modules/autonomous-loop.md b/document/en/modules/autonomous-loop.md
index 0b844bb4..346c5fdc 100644
--- a/document/en/modules/autonomous-loop.md
+++ b/document/en/modules/autonomous-loop.md
@@ -71,7 +71,7 @@ Frontend entry: the **"Autonomous loop" toggle in the chat input** (`components/
## Related code
-- Driver `orchestration/autonomous_loop.py`, planner `orchestration/loop_planner.py`, reviewer `orchestration/subagents/loop_reviewer.py`
-- ChatRun integration `orchestration/chat_run_executor.py` (`start_autonomous_loop_run` / startup reconciliation `resume_running_loops`)
-- Service `core/services/loop_service.py` (ledger mirror / start params / steering queue), API `api/routes/v1/loops.py`
+- Driver `orchestration/loops/driver.py`, planner `orchestration/loops/planner.py`, reviewer `orchestration/loops/reviewer.py`
+- ChatRun integration `core/chat/runtime/` (`start_autonomous_loop_run` / startup reconciliation `resume_running_loops`)
+- Service `orchestration/loops/run.py` (ledger mirror / start params / steering queue), API `api/routes/v1/loops.py`
- Tables `agent_loops` / `loop_iterations`
diff --git a/document/en/modules/browser-automation.md b/document/en/modules/browser-automation.md
index 56149118..883b37c2 100644
--- a/document/en/modules/browser-automation.md
+++ b/document/en/modules/browser-automation.md
@@ -107,7 +107,7 @@ Startup probes the actual Chromium sandbox. Known inner-sandbox failures retry w
After OpenSandbox updates, explicitly build the runtime image from the repository root:
```bash
-docker build -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+docker build -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
```
Ordinary docker compose up --build does not rebuild this on-demand image. Check OPENSANDBOX_IMAGE and create a fresh browser_open session after updating. Resource readiness requires Chromium initialization and real browser verification. Rebuild the corresponding images for other providers too.
diff --git a/document/en/modules/canvas-artifacts.md b/document/en/modules/canvas-artifacts.md
index 707ad1a8..dca1517f 100644
--- a/document/en/modules/canvas-artifacts.md
+++ b/document/en/modules/canvas-artifacts.md
@@ -8,7 +8,7 @@ Personal canvas editing, the artifact center, and chat sharing are all **Communi
## Data canvas
-The data canvas is a slide-out panel on the right of the chat UI (`src/frontend/src/components/canvas/CanvasPanel.tsx`), opened by clicking an attachment card in chat (`components/chat/ArtifactCardList.tsx`) or a file in My Space (`components/myspace/MySpacePanel.tsx`). The panel picks a renderer per file type:
+The data canvas is a slide-out panel on the right of the chat UI (`src/frontend/src/components/canvas/CanvasPanel.tsx`), opened by clicking an attachment card in chat (`components/chat/MessageArtifacts.tsx`) or a file in My Space (`components/myspace/MySpacePanel.tsx`). The panel picks a renderer per file type:
| File type | Rendering | Editable |
|---|---|---|
@@ -87,14 +87,14 @@ limits show a local-download prompt while the main page remains responsive.
### How artifacts are produced and persisted
-During a streamed turn, files produced by tools first enter the per-conversation **workspace** (`core/llm/workspace`); only files that are explicitly **pinned** are written to the `artifacts` table at end of turn by `core/services/artifact_service.py::persist_artifacts` — this strict workspace gate keeps temporary intermediates from polluting My Space.
+During a streamed turn, files produced by tools first enter the per-conversation **workspace** (`core/sandbox/workspace`); only files that are explicitly **pinned** are written to the `artifacts` table at end of turn by `core/files/artifacts/service.py::persist_artifacts` — this strict workspace gate keeps temporary intermediates from polluting My Space.
-Physical storage is managed by `core/artifacts/store.py`, dual-mode by `STORAGE_TYPE`:
+Physical storage is managed by `core/files/artifacts/store.py`, dual-mode by `STORAGE_TYPE`:
- `local` (default, Community Edition): files are written under `${STORAGE_PATH:-result}/artifacts/` with a local JSON index (`index.json`), served via FileResponse.
-- `oss` (Enterprise Edition, `cloud_storage`): files are uploaded to Aliyun OSS; the local index is also backed up to OSS so it survives container restarts. The S3 backend lives in `core/storage/`.
+- `oss` (Enterprise Edition, `cloud_storage`): files are uploaded to Aliyun OSS; the local index is also backed up to OSS so it survives container restarts. The S3 backend lives in `core/files/storage/`.
-Downloads all go through `GET /files/{file_id}` (`api/routes/files.py`), with an owner ∪ team permission check via `core/auth/permissions_iface.py::resolve_artifact_access`; Office files support PDF-converted online preview.
+Downloads all go through `GET /files/{file_id}` (`api/routes/files.py`), with an owner ∪ team permission check via `core/auth/personal_permissions_iface.py::resolve_artifact_access`; Office files support PDF-converted online preview.
### REST API (`/v1/artifacts`, `api/routes/v1/artifacts.py`)
@@ -153,8 +153,8 @@ attachments and My Space, and xlsx files remain editable in the data canvas.
|---|---|
| Canvas panel / Univer spreadsheet | `src/frontend/src/components/canvas/CanvasPanel.tsx`, `UniverSpreadsheet.tsx`, `src/frontend/src/stores/canvasStore.ts` |
| Artifact REST API | `src/backend/api/routes/v1/artifacts.py` |
-| Artifact store (local/OSS) | `src/backend/core/artifacts/store.py` |
-| Artifact persistence (workspace gate) | `src/backend/core/services/artifact_service.py` |
+| Artifact store (local/OSS) | `src/backend/core/files/artifacts/store.py` |
+| Artifact persistence (workspace gate) | `src/backend/core/files/artifacts/service.py` |
| File download / preview | `src/backend/api/routes/files.py` |
| Chat share API | `src/backend/api/routes/v1/chat_shares.py` |
| Share frontend | `src/frontend/src/SharePreviewApp.tsx`, `src/frontend/src/components/share/ShareRecordsPage.tsx` |
diff --git a/document/en/modules/catalog.md b/document/en/modules/catalog.md
index 610135a0..83e1ff2b 100644
--- a/document/en/modules/catalog.md
+++ b/document/en/modules/catalog.md
@@ -13,7 +13,7 @@ core/config/catalog.json system-level single source of truth (admin t
core/config/catalog.py public API: get_catalog / is_enabled / get_enabled_ids
│ set_enabled / reorder_items
▼
-core/config/catalog_resolver.py merge layer: catalog.json defaults ∩ per-user DB overrides
+core/capabilities/catalog/resolver.py merge layer: catalog.json defaults ∩ per-user DB overrides
│ resolve_all_runtime_enabled(db, user_id) → (skills, agents, mcps)
│ resolve_explicit_runtime_capabilities(...) → per-turn invocation (ignores personal switches, keeps hard gates)
▼
@@ -39,7 +39,7 @@ The file lives at `src/backend/core/config/catalog.json` (path overridable via t
}
```
-`catalog_loader.py` performs **dynamic-source synchronization** at load time: new ids discovered from the skill loader (`core/agent_skills/`) and the MCP config service (`core/services/mcp_service.py`, DB table `admin_mcp_servers`) are added automatically; names/descriptions/versions refresh from the source; stale entries whose id no longer exists in any source are removed. Display details (Capability Center intro markdown, icons) are runtime-only fields and never persisted. Results are held in a short-TTL in-memory cache; writes call `invalidate_catalog_cache()`.
+`catalog_loader.py` performs **dynamic-source synchronization** at load time: new ids discovered from the skill loader (`core/capabilities/skills/`) and the MCP config service (`core/capabilities/mcp/service.py`, DB table `admin_mcp_servers`) are added automatically; names/descriptions/versions refresh from the source; stale entries whose id no longer exists in any source are removed. Display details (Capability Center intro markdown, icons) are runtime-only fields and never persisted. Results are held in a short-TTL in-memory cache; writes call `invalidate_catalog_cache()`.
### Gating API
@@ -54,7 +54,7 @@ These two functions are the system-wide read path for capability switches: `fact
## Per-user overrides (catalog_overrides)
-When a user toggles a capability in the Capability Center, the change is written to the `catalog_overrides` DB table (`core/services/catalog_service.py`), per-user and isolated:
+When a user toggles a capability in the Capability Center, the change is written to the `catalog_overrides` DB table (`core/capabilities/catalog/overrides.py`), per-user and isolated:
- **Merge algorithm** (`catalog_resolver.py::_merge_kind`): overrides can only flip the enabled flag of items that *exist* in the base catalog; they can never resurrect deleted items.
- **Admin lock**: items disabled in the base catalog (`enabled=false`) **cannot** be re-enabled by user overrides, and are completely hidden from the user-facing `/v1/catalog` response.
@@ -66,7 +66,7 @@ When a user toggles a capability in the Capability Center, the change is written
- **Explicit plugin guidance**: selecting a plugin loads its skills and MCP tools and recommends using them when relevant, without restricting the first tool call or requiring a call before the answer.
- **Chat-sticky capability restoration**: later turns restore activated skills, connectors, and plugin components before personal-switch filtering, keeping the skill list and tool names/prefixes stable in that chat. Installation, global admin state, dependency readiness, and ownership are revalidated each turn, so hard-gate changes take effect immediately.
-On every chat, `core/chat/context.py::resolve_enabled_capabilities()` writes the default merged result into the workflow context. `api/routes/v1/chats/__init__.py::_resolve_explicit_capability_invocation()` then verifies plugin installations and capability ownership, resolves components from the server-side installation record, and merges hard-gate-approved explicit selections into that turn. On later turns, `core/llm/session_capabilities.py` and `core/plugins/runtime/__init__.py::resolve_sticky_plugin_capabilities()` restore chat-activated capabilities.
+On every chat, `core/chat/context.py::resolve_enabled_capabilities()` writes the default merged result into the workflow context. `api/routes/v1/chats/__init__.py::_resolve_explicit_capability_invocation()` then verifies plugin installations and capability ownership, resolves components from the server-side installation record, and merges hard-gate-approved explicit selections into that turn. On later turns, `core/llm/runtime/session_capabilities.py` and `core/capabilities/plugins/runtime/__init__.py::resolve_sticky_plugin_capabilities()` restore chat-activated capabilities.
## The /v1/catalog route and KB injection
@@ -116,20 +116,19 @@ State is centralized in `src/frontend/src/stores/catalogStore.ts`; local default
## Admin-side capability management
- **`/admin` content console**: skill upload/toggle/reorder (`api/routes/v1/admin_skills.py`, which calls `catalog.set_enabled` / `reorder_items` to write back to catalog.json), MCP server management (`admin_mcp_servers.py`, DB table + connectivity probe + cache refresh), sub-agent management (`admin_agents.py`), and skill marketplace listing review (`admin_marketplace.py`, **Enterprise Edition (EE)**).
-- **Catalog snapshot migration**: `scripts/export_content.py --only catalog` exports catalog.json + `catalog_overrides`; `scripts/import_content.py --catalog ` imports them.
## Source map
| Topic | Path |
|---|---|
-| Public API (is_enabled, etc.) | `src/backend/core/config/catalog.py` |
+| Public API (is_enabled, etc.) | `src/backend/core/capabilities/catalog/` |
| Single-source file | `src/backend/core/config/catalog.json` (overridable via `CATALOG_PATH`) |
-| Loading / dynamic sync / caching | `src/backend/core/config/catalog_loader.py`, `catalog_common.py`, `catalog_migration.py` |
-| User-override merging | `src/backend/core/config/catalog_resolver.py`, `core/services/catalog_service.py` |
+| Loading / dynamic sync / caching | `src/backend/core/capabilities/catalog/loader.py`, `catalog_common.py`, `catalog_migration.py` |
+| User-override merging | `src/backend/core/capabilities/catalog/resolver.py`, `core/capabilities/catalog/overrides.py` |
| Catalog route | `src/backend/api/routes/v1/catalog.py` |
| User self-service capabilities | `src/backend/api/routes/v1/me_capabilities.py` |
-| MCP server config (DB) | `src/backend/core/services/mcp_service.py`, `api/routes/v1/admin_mcp_servers.py` |
-| Skill management | `src/backend/api/routes/v1/admin_skills.py`, `core/agent_skills/` |
-| Dify KB injection (EE) | `src/backend/edition_ee/kb/dify.py`, via the shared `core/kb/external_provider.py` seam |
+| MCP server config (DB) | `src/backend/core/capabilities/mcp/service.py`, `api/routes/v1/admin_mcp_servers.py` |
+| Skill management | `src/backend/api/routes/v1/admin_skills.py`, `core/capabilities/skills/` |
+| Dify KB injection (EE) | `src/backend/edition_ee/kb/dify.py`, via the shared `features/knowledge/extension.py` seam |
| Frontend Capability Center | `src/frontend/src/components/catalog/`, `stores/catalogStore.ts` |
| Factory consumption | `src/backend/core/llm/factory/build.py::_effective_mcp_server_keys` |
diff --git a/document/en/modules/chat.md b/document/en/modules/chat.md
index a5f2fc76..2fc804de 100644
--- a/document/en/modules/chat.md
+++ b/document/en/modules/chat.md
@@ -71,10 +71,10 @@ orchestration/chat_run_executor.py::start_run()
▼
orchestration/workflow.py::astream_chat_workflow()
│ ├─ orchestration/memory_integration.py non-blocking memory retrieval (bg task + budget)
- │ ├─ core/config/catalog_resolver.py resolve enabled skills/mcp/kb for this request
+ │ ├─ core/capabilities/catalog/resolver.py resolve enabled skills/mcp/kb for this request
│ ├─ core/llm/factory/build.py::create_agent_executor()
│ │ MCP pool + skill registration + file tools + system prompt + middlewares → Agent
- │ ├─ core/llm/context_manager.py trim history to the token budget
+ │ ├─ core/llm/context/window_policy.py trim history to the token budget
│ └─ orchestration/streaming.py::StreamingAgent.stream()
│ consumes agent.reply_stream(), coalesces 25 event kinds while preserving tool-argument deltas
▼
@@ -85,7 +85,7 @@ SSE follower: chat_run_executor.follow_run_as_sse()
### Run decoupling and reconnect/resume
-Every sent message creates a `ChatRun` and a background task (`orchestration/chat_run_executor.py`); events are written to a Redis Stream (`maxlen=5000`; the default TTL is the 7,200-second maximum human wait plus a 1,800-second recovery grace period). The HTTP connection is merely a *follower*, which enables:
+Every sent message creates a `ChatRun` and a background task (`core/chat/runtime/`); events are written to a Redis Stream (`maxlen=5000`; the default TTL is the 7,200-second maximum human wait plus a 1,800-second recovery grace period). The HTTP connection is merely a *follower*, which enables:
| Capability | Endpoint |
|---|---|
@@ -150,7 +150,7 @@ A permission chip sits next to the project selector in the composer toolbar. It
| Approve for me | `auto` | Ordinary writes and edits pass silently; **destructive operations** (`DESTRUCTIVE_OPS`) and commands the local policy tagged `danger:` still stop and ask |
| Full access | `full` | No tool confirmation is ever raised |
-The vocabulary is declared once in `core/llm/tool_permissions.py` (`APPROVAL_ASK` / `APPROVAL_AUTO` / `APPROVAL_FULL`), travels with the chat context into `PermissionRuntime`, and is the single criterion every tool confirmation gate consults. No preset does more than skip the *asking* — a hard deny from the local security policy still denies. Whether a given call counts as dangerous is recovered by `core/sandbox/permissions/models.py::danger_categories()` from the `danger:` prefix in the verdict reasons, never by matching prose. Legacy stored values (`standard`, `readonly`) fall back to the most conservative preset, `ask`.
+The vocabulary is declared once in `core/llm/tool_permissions/` (`APPROVAL_ASK` / `APPROVAL_AUTO` / `APPROVAL_FULL`), travels with the chat context into `PermissionRuntime`, and is the single criterion every tool confirmation gate consults. No preset does more than skip the *asking* — a hard deny from the local security policy still denies. Whether a given call counts as dangerous is recovered by `core/sandbox/permissions/models.py::danger_categories()` from the `danger:` prefix in the verdict reasons, never by matching prose. Legacy stored values (`standard`, `readonly`) fall back to the most conservative preset, `ask`.
Trusted unattended entry points (channel bots, scheduled automation) take the `default_allow` branch and are unaffected by the preset.
@@ -160,17 +160,17 @@ Trusted unattended entry points (channel bots, scheduled automation) take the `d
`create_agent_executor()` is the shared factory for every mode (main chat, plan, batch, sub-agents, automation):
-- **MCP tools**: after the three-layer filter of catalog + per-user overrides + request context (see [Capability Center](catalog.md)), stable servers reuse the process-level connection pool (`core/llm/mcp_pool.py`); per-request servers (e.g. `retrieve_dataset_content`, which needs per-request HTTP headers) are spawned fresh; the user's self-added private MCP servers are merged in with owner isolation.
-- **Skills**: registered as AgentScope Agent Skills via `core/agent_skills/loader.py`, with `view_text_file` allow-listed to read SKILL.md files (see [Agent Skills](agent-skills.md)).
+- **MCP tools**: after the three-layer filter of catalog + per-user overrides + request context (see [Capability Center](catalog.md)), stable servers reuse the process-level connection pool (`core/llm/mcp/`); per-request servers (e.g. `retrieve_dataset_content`, which needs per-request HTTP headers) are spawned fresh; the user's self-added private MCP servers are merged in with owner isolation.
+- **Skills**: registered as AgentScope Agent Skills via `core/capabilities/skills/loader.py`, with `view_text_file` allow-listed to read SKILL.md files (see [Agent Skills](agent-skills.md)).
- **File / sandbox tools**: `bash`, `sandbox_put_artifact`, `sandbox_get_artifact` are always registered; Read/Edit/Write/Glob/Grep/Delete/Move/mkdir plus the MySpace tools are gated by `CODE_CAPABILITY_ENABLED` and share one `ReadStateTracker` to keep the "must Read before Edit" invariant.
-- **Middlewares** (onion model, `core/llm/middlewares.py`): `DynamicModelMiddleware` (switches the model per chat_mode, see [Model Providers](model-providers.md)), `FileContextMiddleware` (injects uploaded/historical file context), `SteerMiddleware` (injects follow-ups after tool results and before the next reasoning round), `WorkspacePinHintMiddleware`, `FinishPinGuardMiddleware`.
+- **Middlewares** (onion model, `core/llm/runtime/`): `DynamicModelMiddleware` (switches the model per chat_mode, see [Model Providers](model-providers.md)), `FileContextMiddleware` (injects uploaded/historical file context), `SteerMiddleware` (injects follow-ups after tool results and before the next reasoning round), `WorkspacePinHintMiddleware`, `FinishPinGuardMiddleware`.
- **Context compression**: `CompactingAgent` routes the pre-turn, mid-turn, and post-turn trigger points through one persistent checkpoint engine and one Codex-style handoff prompt. `ContextConfig` remains only for tool-result limiting. Compaction uses the unified coordinator without a second framework summary fallback. Failure preserves the original context. If the final request still exceeds its budget, the run saves a checkpoint and pauses before calling the reply model.
- **Permissions**: every registered tool gets a native `PermissionRule(ALLOW)` seed, preserving AgentScope's built-in dangerous-operation checks (no blanket BYPASS).
- **Iteration caps**: main agent defaults to `max_iters=50`, isolated sub-agents to 10.
## SSE event types and payloads
-`orchestration/streaming.py::StreamingAgent` coalesces AgentScope 2.0 `reply_stream` events into internal events. Tiny tool-argument fragments are batched at 256 characters or 50ms, keeping them visible without recreating an SSE event storm. `workflow.py` and `chats.py::_stream_sse_response` enrich them with chat-level fields before they hit the wire. Events as the frontend sees them:
+`core/llm/runtime/streaming.py::StreamingAgent` coalesces AgentScope 2.0 `reply_stream` events into internal events. Tiny tool-argument fragments are batched at 256 characters or 50ms, keeping them visible without recreating an SSE event storm. `workflow.py` and `chats.py::_stream_sse_response` enrich them with chat-level fields before they hit the wire. Events as the frontend sees them:
| `type` | Meaning | Key fields |
|---|---|---|
@@ -219,7 +219,7 @@ data: [DONE]
After `meta`, `chat_run_executor.py` persists the assistant message, backfills artifacts,
and launches a background follow-up-question generator
-(`orchestration/followups.py`; results land in the message's
+(`core/chat/followups.py`; results land in the message's
`extra_data.follow_up_questions` and are fetched through
`GET /v1/chats/{chat_id}/messages/{message_id}/followups`). The frontend
collects ontology events in a standalone **Domain Ontology Governance** module
@@ -272,9 +272,9 @@ Each canonical tool-result step is persisted before its display event, independe
Citations make every fact in the answer traceable back to a specific tool result. Numbering authority belongs to a single backend source of truth — the model only **copies** ids, never computes them. The chain has four segments:
-1. **Anchor allocation & injection (backend middleware)**: `core/llm/middlewares.py::CitationAnchorMiddleware` hooks AgentScope 2.0's `on_acting` and, before a tool result reaches the model, calls `orchestration/citation_anchor.py` to extract → allocate → inject: each citable item gets a session-monotonic anchor id (`e1`, `e2`, … — unique across tools, calls, and turns; a new turn continues from the max anchor found in the chat's persisted messages), and `"cite_id": "e7"` is written into the result JSON in place (plain-text results get a trailing `[cite_id: e7]` line). **The allocator is bound to the agent instance** (`attach_allocator()` / `resolve_allocator()`), which is how the orchestrator and the middleware share one counter; the ContextVar is only a fallback for sub-agent chains, because `astream_chat_workflow` is an async generator whose context does not reach the task the agent actually runs in. Extraction degrades through four layers: tool-declared `__citations__` → the tool spec registry (`TOOL_SPECS` config: list paths + CN/EN field aliases) → a generic heuristic (unique dict-array field) → the whole result as one anchor. Operational tools (file writes, pin, etc. — `SKIP_TOOLS`) pass through untouched. Any exception passes the original result through — citations degrade, the conversation never breaks.
+1. **Anchor allocation & injection (backend middleware)**: `core/llm/runtime/` hooks AgentScope 2.0's `on_acting` and, before a tool result reaches the model, calls `core/llm/citations/hook.py` to extract → allocate → inject: each citable item gets a session-monotonic anchor id (`e1`, `e2`, … — unique across tools, calls, and turns; a new turn continues from the max anchor found in the chat's persisted messages), and `"cite_id": "e7"` is written into the result JSON in place (plain-text results get a trailing `[cite_id: e7]` line). **The allocator is bound to the agent instance** (`attach_allocator()` / `resolve_allocator()`), which is how the orchestrator and the middleware share one counter; the ContextVar is only a fallback for sub-agent chains, because `astream_chat_workflow` is an async generator whose context does not reach the task the agent actually runs in. Extraction degrades through four layers: tool-declared `__citations__` → the tool spec registry (`TOOL_SPECS` config: list paths + CN/EN field aliases) → a generic heuristic (unique dict-array field) → the whole result as one anchor. Operational tools (file writes, pin, etc. — `SKIP_TOOLS`) pass through untouched. Any exception passes the original result through — citations degrade, the conversation never breaks.
2. **Prompt contract**: the system prompt (fallback file `prompts/prompt_text/default/system/40_format.system.md`; the active DB version is authoritative at runtime) needs only one tool-count-independent rule: copy the `cite_id` annotated in the result verbatim into `[anchor text](cite:e7)` (or `[来源](cite:e7)` at sentence end); never self-number.
-3. **Orchestration consumption**: each `tool_result` event calls `collect_citation_dicts()`, which fetches `CitationItem`s (`id` / `tool_name` / `tool_id` / `title` / `url` / `snippet` / `source_type` / `item_index`) from the allocator registry keyed by `tool_id`; when no allocator is installed (legacy replay paths) it falls back to the old offset extraction in `orchestration/citations.py`. `source_type` values: CE ships `internet`, `knowledge_base`, and `database`; industry citation types such as `industry_news`, `ai_news`, `chain_info`, and `company_profile` are added by the Enterprise Edition (EE).
+3. **Orchestration consumption**: each `tool_result` event calls `collect_citation_dicts()`, which fetches `CitationItem`s (`id` / `tool_name` / `tool_id` / `title` / `url` / `snippet` / `source_type` / `item_index`) from the allocator registry keyed by `tool_id`; when no allocator is installed (legacy replay paths) it falls back to the old offset extraction in `core/llm/citations/`. `source_type` values: CE ships `internet`, `knowledge_base`, and `database`; industry citation types such as `industry_news`, `ai_news`, `chain_info`, and `company_profile` are added by the Enterprise Edition (EE).
4. **Frontend rendering**: citations ride on `tool_result` and `meta` events and are persisted with the message (what is persisted is the annotated result, so replay/share shows the same numbering as generation). `components/citation/CitationMarkdownBlock.tsx` recognizes three marker forms in parallel — `[anchor text](cite:eN)` (rendered as a text link with a hover source card), `[[eN]]` (obsidian-style tolerance), and the legacy `[ref:tool_name-N]` (historical messages, rendered as a superscript badge). Tool cards show a matching `cite_id` chip on each item (`jx-tr-citeTag`), and tools without a dedicated renderer fall back to a generic list-card renderer.
**Tool development convention**: a tool (in-house or MCP) that wants precise citation granularity should return a `__citations__` field in its JSON — `[{"title": "...", "url": "...", "snippet": "...", "source_type": "..."}, …]`, entries ordered to match the result body; the middleware adopts it verbatim and injects `cite_id` in place. Tools without the field fall back to registry config or heuristics — at worst the whole result becomes one anchor, so **every tool is citable by default**. See the citation-declaration section in [MCP tools](mcp-tools.md).
@@ -297,7 +297,7 @@ Cards are persisted with the message (`metadata.referenced_chats`) and history r
## Plan Mode
-Plan Mode splits complex tasks into "generate plan → user reviews/edits → execute step by step", implemented in `orchestration/subagents/plan_mode.py`:
+Plan Mode splits complex tasks into "generate plan → user reviews/edits → execute step by step", implemented in `orchestration/plans/`:
- **Generate** (`astream_generate_plan` / `POST /v1/plans/generate`): a "bare LLM" agent (`disable_tools=True`) produces a structured JSON plan. System-prompt resolution: active `plan_mode` version in the prompt pool → legacy `system/90_plan_mode` part → fallback file `prompts/prompt_text/plan_mode/plan_mode.system.md` → hardcoded minimal prompt.
- **Execute** (`astream_execute_plan` / `POST /v1/plans/{plan_id}/execute`): each step gets its own agent, executed sequentially, with step-level MCP/skill/sub-agent bindings and cancellation (`is_run_cancelled` polling); execution also goes through ChatRun + Redis Stream, so it survives disconnects.
@@ -351,7 +351,7 @@ the `subagents` prompt-pool kind. That kind stays in backend storage and
migration snapshots for compatibility;
`extensions/agents/default//system.md` is only the seed and failure fallback.
-In addition, user-created sub-agents (`api/routes/v1/agents.py`, DB table `UserAgent`)
+In addition, user-created sub-agents (`edition_ee/routes/agents.py`, DB table `UserAgent`)
can carry their own system prompt, MCP, skill, plugin, and knowledge base
bindings, plus model parameters such as provider, temperature, `max_tokens`,
and `max_iters`. When you create or edit a sub-agent, the resource picker
@@ -428,7 +428,7 @@ or no-longer-owned components are not restored.
- **Autonomous main-agent dispatch**: when neither a structured `@` selection
nor the strict natural-language command matches, the main agent can use the
`call_subagent` tool registered by
- `core/llm/subagent_tool.py`. Each child runs in its own thread and event loop,
+ `core/llm/subagents/tool.py`. Each child runs in its own thread and event loop,
then returns text for the main agent to integrate. This path supports parallel
sub-agents, task decomposition, and cross-domain synthesis.
@@ -438,8 +438,8 @@ Three complementary layers:
| Layer | Implementation | Trigger |
|---|---|---|
-| Chat title summary | `core/llm/summarizer.py::ConversationSummarizer` (`summarizer` model role, `ENABLE_SUMMARY` flag), `POST /v1/summary` | Auto-titling new chats |
-| Unified context checkpoint | `core/services/compaction_service.py::run_compaction()`; pre-turn, mid-turn, and post-turn share one trigger ratio, handoff prompt, replacement shape, and persistent checkpoint | Context reaches the configured fraction of the model window |
+| Chat title summary | `core/models/completions/summarizer.py::ConversationSummarizer` (`summarizer` model role, `ENABLE_SUMMARY` flag), `POST /v1/summary` | Auto-titling new chats |
+| Unified context checkpoint | `core/chat/compaction/service.py::run_compaction()`; pre-turn, mid-turn, and post-turn share one trigger ratio, handoff prompt, replacement shape, and persistent checkpoint | Context reaches the configured fraction of the model window |
| Deterministic overflow protection | `ContextConfig.tool_result_limit` bounds individual tool results first; failed compaction preserves the original context, and an over-budget final request saves a checkpoint and pauses | A tool result is oversized or unified compaction is temporarily unavailable |
Compaction cuts only at legal boundaries: history is first split into steps,
@@ -468,7 +468,7 @@ recovery stay consistent.
## Oversized tool-result offloading
-`core/llm/offloader.py::SandboxOffloader` implements the AgentScope 2.0 `Offloader` protocol: when context compression or tool-result truncation happens, the overflow is no longer silently discarded — it is written into the sandbox at `/workspace/.offload/` (`tool_.txt` / `context_.txt`), the framework appends the path to the model-facing ``, and the model can read it back on demand via `Read` / `bash`. Mounted only when sandbox tools are enabled (`SANDBOX_TOOLS_ENABLED=true`, default on); write failures never raise and degrade to an explanatory message.
+`core/llm/context/offloader.py::SandboxOffloader` implements the AgentScope 2.0 `Offloader` protocol: when context compression or tool-result truncation happens, the overflow is no longer silently discarded — it is written into the sandbox at `/workspace/.offload/` (`tool_.txt` / `context_.txt`), the framework appends the path to the model-facing ``, and the model can read it back on demand via `Read` / `bash`. Mounted only when sandbox tools are enabled (`SANDBOX_TOOLS_ENABLED=true`, default on); write failures never raise and degrade to an explanatory message.
## Chat sharing
@@ -486,29 +486,29 @@ Storage is Redis (`chat_share:*` key groups + TTL) with an in-process memory fal
## Other entry points
-The same orchestration foundation also powers: response regeneration (`POST /v1/chats/{chat_id}/regenerate`), edit-and-resend (`POST /v1/chats/{chat_id}/edit`), non-streaming `POST /v1/agents/responses` with `stream: false` (see [Automation](automation.md)), and scheduled automation (`orchestration/schedulers/`).
+The same orchestration foundation also powers: response regeneration (`POST /v1/chats/{chat_id}/regenerate`), edit-and-resend (`POST /v1/chats/{chat_id}/edit`), non-streaming `POST /v1/agents/responses` with `stream: false` (see [Automation](automation.md)), and scheduled automation (`orchestration/automations/`).
## Source map
| Topic | Path |
|---|---|
| Chat routes / SSE egress | `src/backend/api/routes/v1/chats/__init__.py` |
-| Run decoupling / Redis Stream / resume | `src/backend/orchestration/chat_run_executor.py`, `api/routes/v1/chat_runs.py` |
-| Streaming orchestration | `src/backend/orchestration/workflow.py` |
-| Event mapping (reply_stream → SSE) | `src/backend/orchestration/streaming.py` |
+| Run decoupling / Redis Stream / resume | `src/backend/core/chat/runtime/`, `api/routes/v1/chat_runs.py` |
+| Streaming orchestration | `src/backend/core/agent_runtime/runner.py` |
+| Event mapping (reply_stream → SSE) | `src/backend/core/llm/runtime/streaming.py` |
| Runtime context assembly | `src/backend/core/chat/context.py` |
| Agent factory | `src/backend/core/llm/factory/build.py` |
-| Middlewares | `src/backend/core/llm/middlewares.py` (pure-function helpers in `core/llm/hooks.py`) |
-| Citation extraction | `src/backend/orchestration/citations.py` |
-| Cross-chat references (cards / digests / paging) | `src/backend/core/services/chat_reference_service.py` |
+| Middlewares | `src/backend/core/llm/runtime/` (pure-function helpers in `core/harness/hooks.py`) |
+| Citation extraction | `src/backend/core/llm/citations/` |
+| Cross-chat references (cards / digests / paging) | `src/backend/core/chat/references.py` |
| Cross-chat history tools | `src/backend/core/llm/tools/chat_history_tool.py` |
| Citation rendering | `src/frontend/src/utils/citations.ts`, `src/frontend/src/components/citation/` |
-| Plan mode | `src/backend/orchestration/subagents/plan_mode.py`, `api/routes/v1/plans.py` |
-| Sub-agent tool | `src/backend/core/llm/subagent_tool.py`, `api/routes/v1/agents.py` |
-| Title summarization / context compaction / window protection | `src/backend/core/llm/summarizer.py`, `compaction.py`, `core/services/compaction_service.py`, `context_manager.py` |
-| Oversized-result offloading | `src/backend/core/llm/offloader.py` |
+| Plan mode | `src/backend/orchestration/plans/`, `api/routes/v1/plans.py` |
+| Sub-agent tool | `src/backend/core/llm/subagents/tool.py`, `edition_ee/routes/agents.py` |
+| Title summarization / context compaction / window protection | `src/backend/core/models/completions/summarizer.py`, `compaction.py`, `core/chat/compaction/service.py`, `context_manager.py` |
+| Oversized-result offloading | `src/backend/core/llm/context/offloader.py` |
| Chat sharing | `src/backend/api/routes/v1/chat_shares.py` |
-| Follow-up generation | `src/backend/orchestration/followups.py` |
+| Follow-up generation | `src/backend/core/chat/followups.py` |
| Frontend stream parsing / follow-up queue | `src/frontend/src/hooks/chatStream.ts`, `useStreaming.ts`, `components/chat/QueuedMessageCard.tsx` |
### Desktop tool permissions and operation confirmation
diff --git a/document/en/modules/knowledge-base-wiki.md b/document/en/modules/knowledge-base-wiki.md
index db1ad00b..a25a7c8c 100644
--- a/document/en/modules/knowledge-base-wiki.md
+++ b/document/en/modules/knowledge-base-wiki.md
@@ -140,12 +140,12 @@ The tools work for both self-hosted and external knowledge bases; just pass the
| Layer | Location |
|---|---|
-| Data model | `src/backend/core/db/models/kb_wiki.py` |
-| Pipeline | `src/backend/core/kb/wiki/` (`extract` / `cite` / `dedup` / `taxonomy` / `reduce` / `finalize` / `pipeline`) |
-| Prompts | `src/backend/core/kb/wiki/prompts.py` |
-| Jobs and worker | `src/backend/core/kb/wiki/jobs.py`, `worker.py` |
-| Read surface | `src/backend/core/kb/wiki/local_provider.py` |
-| Per-base dispatch | `src/backend/core/kb/wiki_router.py` |
-| Routes | `src/backend/api/routes/v1/kb_wiki.py` |
+| Data model | `src/backend/features/knowledge/models.py` |
+| Pipeline | `src/backend/features/knowledge/wiki/` (`extract` / `cite` / `dedup` / `taxonomy` / `reduce` / `finalize` / `pipeline`) |
+| Prompts | `src/backend/features/knowledge/wiki/prompts.py` |
+| Jobs and worker | `src/backend/features/knowledge/wiki/jobs.py`, `worker.py` |
+| Read surface | `src/backend/features/knowledge/wiki/local_provider.py` |
+| Per-base dispatch | `src/backend/features/knowledge/wiki_router.py` |
+| Routes | `src/backend/features/knowledge/api/kb_wiki.py` |
| Agent tools | `src/backend/extensions/mcp_servers/retrieve_dataset_content_mcp/wiki_impl.py` |
| Frontend | `src/frontend/src/components/kb/` (`WikiPanel` / `WikiTree` / `ConceptGraph` / `IndexModePicker`) |
diff --git a/document/en/modules/knowledge-base.md b/document/en/modules/knowledge-base.md
index 1e84aa15..32edc8ed 100644
--- a/document/en/modules/knowledge-base.md
+++ b/document/en/modules/knowledge-base.md
@@ -29,7 +29,7 @@ In CE, the frontend shows only **Private Knowledge Base**, and `/v1/catalog` ret
Object storage write (storage_key) │ · embed_text(query)
│ │ · Milvus hybrid_search:
▼ BackgroundTask │ dense (IP) + sparse (BoW) → RRF(k=60)
- core/content/kb_processing.py │ · child hit → fetch parent chunk
+ core/files/content/kb_processing.py │ · child hit → fetch parent chunk
vectorise_document_background() │ text from kb_chunks (PostgreSQL)
· kb_parser.parse_and_chunk() │ · optional reranker pass
· (optional) LLM keywords / questions │ · user_id isolation + global public KBs
@@ -68,9 +68,9 @@ ORM definitions live in `src/backend/core/db/models/knowledge.py`:
| `kb_chunks` | **Parent chunk** text (returned to the LLM on retrieval), with `tags` and related `questions` |
| `kb_assets` | **Media assets** carried by a document (figures cut out by layout parsing, standalone image uploads): bytes live in object storage; this table holds the owning chunk, the source position `locator`, the caption/OCR text `text_content`, and the model-generated description `caption` |
-The three Wiki tables (`kb_wiki_pages` / `kb_wiki_folders` / `kb_wiki_jobs`) are defined in `core/db/models/kb_wiki.py`; see [Knowledge Base Wiki](./knowledge-base-wiki.md).
+The three Wiki tables (`kb_wiki_pages` / `kb_wiki_folders` / `kb_wiki_jobs`) are defined in `features/knowledge/models.py`; see [Knowledge Base Wiki](./knowledge-base-wiki.md).
-Child chunks never touch the relational DB — they are vectorized into the Milvus collection `hugagent_kb_private` (`core/kb/kb_vector.py`); every row carries `user_id` / `kb_id` for ownership isolation, and `row_type` distinguishes chunk rows, question rows, and image rows.
+Child chunks never touch the relational DB — they are vectorized into the Milvus collection `hugagent_kb_private` (`features/knowledge/kb_vector.py`); every row carries `user_id` / `kb_id` for ownership isolation, and `row_type` distinguishes chunk rows, question rows, and image rows.
### Chunking and indexing
@@ -84,12 +84,12 @@ Parsing and chunking happen in `core/kb/kb_parser.py::parse_and_chunk()`, suppor
| `recursive` | Recursive fixed-size splitting |
| `embedding_semantic` | Semantic boundary detection via embedding similarity |
-Parent-child parameters are tunable per upload via `indexing_config`: `parent_chunk_size` (default 1024 tokens), `child_chunk_size` (128), `overlap_tokens` (20), `parent_child_indexing` (default true). LLM enrichment is opt-in: `auto_keywords_count` (keywords per parent chunk, stored as tags and fed into sparse retrieval) and `auto_questions_count` (generated questions per parent chunk, indexed as separate question rows in Milvus to boost question-style recall). The background vectorization task is `core/content/kb_processing.py::vectorise_document_background()`.
+Parent-child parameters are tunable per upload via `indexing_config`: `parent_chunk_size` (default 1024 tokens), `child_chunk_size` (128), `overlap_tokens` (20), `parent_child_indexing` (default true). LLM enrichment is opt-in: `auto_keywords_count` (keywords per parent chunk, stored as tags and fed into sparse retrieval) and `auto_questions_count` (generated questions per parent chunk, indexed as separate question rows in Milvus to boost question-style recall). The background vectorization task is `core/files/content/kb_processing.py::vectorise_document_background()`.
### Multimodal: image retrieval
Figures inside documents are no longer discarded. Indexing runs through
-`core/kb/kb_assets.py`:
+`features/knowledge/kb_assets.py`:
1. **Extraction** — for PDFs, the external parse service's layout pass returns the figure
bytes along with its caption, page index and bbox; a standalone image upload is itself
@@ -98,7 +98,7 @@ Figures inside documents are no longer discarded. Indexing runs through
service's temp paths are rewritten to real asset URLs.
2. **Image understanding** — reuses the
[vision bridge](./model-providers.md#vision-bridge-giving-text-only-models-eyes)
- (`core/vision`): the same path and the same `vision` model role that lets a text-only
+ (`core/models/vision`): the same path and the same `vision` model role that lets a text-only
chat model read images. Three things come for free with it — an **evidence cache keyed
by image content** (re-indexing the same document costs nothing), three provider
protocols with structured-output degradation, and the hard constraint that only a
@@ -149,7 +149,7 @@ Returned content follows the `[ref:retrieve_local_kb-N]` citation-marker convent
### API routes
-User-facing routes are prefixed `/v1/catalog/kb` (`src/backend/api/routes/v1/kb.py`, request models in `kb_models.py`):
+User-facing routes are prefixed `/v1/catalog/kb` (`src/backend/features/knowledge/api/kb.py`, request models in `kb_models.py`):
| Method | Path | Description |
|---|---|---|
@@ -165,7 +165,7 @@ User-facing routes are prefixed `/v1/catalog/kb` (`src/backend/api/routes/v1/kb.
| GET / PATCH | `/v1/catalog/kb/{kb_id}/chunks[/{chunk_id}]` | Chunk list / edit tags and questions |
| GET | `/v1/catalog/kb/assets/{asset_id}` | Read a media asset's raw bytes (visibility follows the owning space) |
-Business logic is centralized in `core/services/kb_service.py::KBService`.
+Business logic is centralized in `features/knowledge/service.py::KBService`.
### System-managed space: MySpace sync
@@ -173,13 +173,13 @@ Business logic is centralized in `core/services/kb_service.py::KBService`.
### Admin public knowledge bases (Enterprise Edition, EE)
-The `/v1/admin/kb/*` admin routes live in `src/backend/api/routes/v1/admin_kb.py`, gated by the `content_admin` feature flag (EE router table in `api/routes/v1/__init__.py`). Public spaces are owned by the synthetic system account `system_public_kb` (`kb_service.py::SYSTEM_KB_OWNER_ID`), have `visibility=public`, and are visible and searchable by all users. The admin side additionally offers raw file download, Office-to-PDF online preview, and chunk content editing / deletion.
+The `/v1/admin/kb/*` admin routes live in `src/backend/edition_ee/routes/admin_kb.py`, gated by the `content_admin` feature flag (EE router table in `api/routes/v1/__init__.py`). Public spaces are owned by the synthetic system account `system_public_kb` (`kb_service.py::SYSTEM_KB_OWNER_ID`), have `visibility=public`, and are visible and searchable by all users. The admin side additionally offers raw file download, Office-to-PDF online preview, and chunk content editing / deletion.
Public retrieval through `retrieve_dataset_content` selects exactly one source according to platform configuration. Platform-local mode (`knowledge_base.provider=custom`) searches only local public/shared spaces accessible to the current user, excluding private spaces. External mode searches only the selected Dify, FastGPT or WeKnora backend using that backend's own connection settings. Results are never merged across sources, and empty results or errors never trigger a cross-source fallback. The public knowledge-base list uses the same source selection; private spaces remain accessible through the separate `retrieve_local_kb` tool.
## External Dify knowledge bases (Enterprise Edition, EE)
-The EE-only client is `src/backend/edition_ee/kb/dify.py`; shared routes call it through `core/kb/external_provider.py`. The derived CE tree replaces that seam with a disabled implementation and contains no Dify client. The `is_dify_enabled()` decision has three priority levels:
+The EE-only client is `src/backend/edition_ee/kb/dify.py`; shared routes call it through `features/knowledge/extension.py`. The derived CE tree replaces that seam with a disabled implementation and contains no Dify client. The `is_dify_enabled()` decision has three priority levels:
1. DB system config `knowledge_base.provider == "dify"` (editable in the Config console);
2. Environment variable `KNOWLEDGE_BASE=dify`;
@@ -195,9 +195,9 @@ DIFY_API_KEY=dataset-... # alias: DIFY_AUTH_TOKEN
## Supported file formats
-KB upload validation is in `core/content/file_validation.py::validate_kb_file` (extension + magic-bytes double check), allowing: `.pdf` `.txt` `.md` `.doc` `.docx` `.xls` `.xlsx` `.csv` `.json`, plus images (`.png` `.jpg` `.jpeg` `.webp` `.gif`).
+KB upload validation is in `core/files/content/file_validation.py::validate_kb_file` (extension + magic-bytes double check), allowing: `.pdf` `.txt` `.md` `.doc` `.docx` `.xls` `.xlsx` `.csv` `.json`, plus images (`.png` `.jpg` `.jpeg` `.webp` `.gif`).
-The general-purpose parser `core/content/file_parser.py::parse_file()` (shared by chat attachments and MySpace files) covers a wider range: PDF, DOCX, DOC/WPS (via LibreOffice conversion), TXT, XLSX/XLS, CSV, PPTX, plus plain-text formats (HTML / Markdown / JSON / YAML / source code, etc.) decoded directly as UTF-8.
+The general-purpose parser `core/files/content/file_parser.py::parse_file()` (shared by chat attachments and MySpace files) covers a wider range: PDF, DOCX, DOC/WPS (via LibreOffice conversion), TXT, XLSX/XLS, CSV, PPTX, plus plain-text formats (HTML / Markdown / JSON / YAML / source code, etc.) decoded directly as UTF-8.
## Frontend
@@ -213,17 +213,17 @@ The general-purpose parser `core/content/file_parser.py::parse_file()` (shared b
| Path | Responsibility |
|---|---|
-| `src/backend/core/kb/kb_parser.py` | Document parsing + parent-child chunking (5 chunk methods) |
-| `src/backend/core/kb/kb_vector.py` | Milvus collection, embedding, hybrid search, reranking |
-| `src/backend/core/kb/kb_assets.py` | Media assets: storage, image understanding (via the vision bridge), retrieval rows, result attachment |
+| `src/backend/features/knowledge/kb_parser.py` | Document parsing + parent-child chunking (5 chunk methods) |
+| `src/backend/features/knowledge/kb_vector.py` | Milvus collection, embedding, hybrid search, reranking |
+| `src/backend/features/knowledge/kb_assets.py` | Media assets: storage, image understanding (via the vision bridge), retrieval rows, result attachment |
| `src/backend/edition_ee/kb/dify.py` | Dify datasets client and enablement logic (EE only) |
-| `src/backend/core/kb/external_provider.py` | Edition-neutral external-provider seam; disabled by the CE overlay |
-| `src/backend/core/content/kb_processing.py` | Background vectorization, LLM keyword / question enrichment |
-| `src/backend/core/content/file_validation.py` | Upload validation (extension + magic bytes) |
-| `src/backend/core/content/file_parser.py` | General-purpose file parser |
-| `src/backend/core/services/kb_service.py` | KB business logic (incl. the system-managed sync space) |
-| `src/backend/api/routes/v1/kb.py` + `kb_models.py` | User-facing `/v1/catalog/kb` routes |
-| `src/backend/api/routes/v1/admin_kb.py` | Admin public KB routes (Enterprise Edition, EE) |
+| `src/backend/features/knowledge/extension.py` | Edition-neutral external-provider seam; disabled by the CE overlay |
+| `src/backend/features/knowledge/processing.py` | Background vectorization, LLM keyword / question enrichment |
+| `src/backend/core/files/content/file_validation.py` | Upload validation (extension + magic bytes) |
+| `src/backend/core/files/content/file_parser.py` | General-purpose file parser |
+| `src/backend/features/knowledge/service.py` | KB business logic (incl. the system-managed sync space) |
+| `src/backend/features/knowledge/api/kb.py` + `kb_models.py` | User-facing `/v1/catalog/kb` routes |
+| `src/backend/edition_ee/routes/admin_kb.py` | Admin public KB routes (Enterprise Edition, EE) |
| `src/backend/api/routes/v1/catalog.py` | Catalog aggregation (private only in CE; Dify / public spaces added in EE) |
| `src/backend/extensions/mcp_servers/retrieve_dataset_content_mcp/` | Retrieval MCP server (both tools) |
| `src/backend/core/db/models/knowledge.py` | `KBSpace` / `KBDocument` / `KBChunk` / `KBAsset` ORM |
diff --git a/document/en/modules/mcp-tools.md b/document/en/modules/mcp-tools.md
index 6f5bad94..4a36c029 100644
--- a/document/en/modules/mcp-tools.md
+++ b/document/en/modules/mcp-tools.md
@@ -29,7 +29,7 @@ must execute in separate rounds.
│ agent) │ │ │ :9103 unified Industry Knowledge Center (EE)│
└─────────┘ │ │ :9104 reserved (retired chart MCP) │
│ │ │ :9105 reserved (former report export MCP) │
- MCPConnectionPool │ │ :9106 web_fetch │
+ Native MCP Client │ │ :9106 web_fetch │
mcp_pool.py) └───┬────┘ :9108 automation_task (automation plugin) │
│ 9109–9111 reserved (former office MCPs) │
│ :9112 skill_manager │
@@ -82,7 +82,7 @@ It is the only **per-request** server: for each chat request the backend injects
### internet_search — web search (CE)
`internet_search(query, queries, max_results, topic, search_depth, include_raw_content)`
-selects Tavily, Baidu, or LangSearch through `INTERNET_SEARCH_ENGINE`, reading only the selected API key.
+selects Tavily, Baidu, LangSearch, or DeepSeek through `INTERNET_SEARCH_ENGINE`, reading only the selected API key.
Supply either one `query` or up to four distinct `queries`. Queries run concurrently; results are interleaved
in input-query order and deduplicated by URL, with at most eight sources. The default is five;
set `max_results=8` for research. Sources are not filtered by language. Tavily receives a country preference
@@ -91,6 +91,9 @@ only when explicitly configured and `topic=general`; there is no implicit China
Public information and technical research can use search directly. Internal business questions prioritize
the relevant internal authority. When snippets cannot support a key claim, use `web_fetch` to verify the
original page. Tavily supports topic, depth and raw content; LangSearch maps summary to content.
+DeepSeek maps summary to content and `include_raw_content` to page content, limits each query to 128 characters
+(`query_too_long`), restricts `topic=news` to the last 30 days with `after:` (dropping undated pages), and drops
+rows with `rerank_score` below 0.5.
The single source list is `result.results`, with title, URL, content, available publication date, retrieval
time and matched query indices. Baidu's original date is preserved as `date_raw`; retrieval time never
@@ -223,7 +226,7 @@ priority order:
injects `"cite_id": "eN"` into each entry in place; the model copies the id as-is.
2. **Standard list shape → one registry line**: list-style tools returning
`{"items": [{"title": …, "content": …}]}` just need an `items_paths` + field-alias
- entry in `orchestration/citation_anchor.py::TOOL_SPECS` — per-item numbering with no
+ entry in `core/llm/citations/hook.py::TOOL_SPECS` — per-item numbering with no
tool code change.
3. **Do nothing → automatic fallback**: a generic heuristic finds the unique dict-array
field at the top level (or one level under `result`) and numbers items; when nothing is
@@ -237,21 +240,18 @@ before the result enters the model context and before persistence; the frontend
render `cite_id` as per-item chips that match the `[anchor text](cite:eN)` references in
the answer body.
-## Backend client: connection pool & bare-name restoration
+## Backend client: native MCP lifetimes
-The backend connects through AgentScope 2.0's `MCPClient`, centred on two files:
+The backend uses MCP SDK 2.3 `Client(mode="auto")` and AgentScope `ToolBase`. `client.py` owns transport authorization; `lifetime.py` owns SDK scopes and complete paginated discovery. HTTP calls reuse the same client throughout a run. STDIO sessions use exclusive leases scoped to the executing user, chat and configuration; successful sessions remain idle for 60 seconds, while failed or cancelled calls destroy them. There is no stable-connection switch or startup connection pool.
-- **`core/llm/mcp_pool.py` — `MCPConnectionPool`** (process-level singleton): at startup `warmup_mcp_tools()` reads all enabled server configs from the DB and pre-connects. Pooling semantics under 2.0:
- - **stdio servers marked `is_stable=true`** keep their connection across requests (saving the 1–7 s subprocess cold start);
- - **HTTP servers are never pooled** — 2.0's stateful HTTP client is bound to its asyncio task, and reuse across requests triggers cancel-scope crashes, so each request gets a fresh `is_stateful=False` connection;
- - per-request servers (KB retrieval with user headers) connect on demand and are closed via `close_transient()` when the request ends.
-- **`core/llm/mcp_manager.py` — `BareNameMCPClient`**: AgentScope 2.0 rewrites tool names to `mcp____`; this subclass restores the server-side bare name (`internet_search`, not `mcp__internet_search__internet_search`) so the display-name mapping (`core/config/display_names.py`), [citation extraction](chat.md) keyed by tool name, and frontend icon rendering all keep working as in 1.x.
+`callbacks.py` binds isolated text sampling and user elicitation to the active run. Resources, URI templates and prompts become permission-governed tools. Progress reaches the existing tool card. Structured results and resource links remain in tool metadata and model-facing output. SDK Tasks extensions are not available in 2.3.
+
+MCP execution budgets exclude time waiting for user answers. Valid questions pause the current invocation; answering or cancelling a question resumes its remaining budget. Repeated or overlapping questions do not reset that budget. Other invocations keep independent timers. Existing human-interaction deadlines still apply, and cancelling a run clears pending questions. Invocation budgets bound SDK requests and HTTP reads; connection, write and discovery timeouts remain active.
-The 2.0 `Toolkit` is constructed once, in `core/llm/factory/build.py`: `Toolkit(tools=[...], mcps=clients)`.
## Unified tool-permission gateway
-`core/llm/tool_permissions.py` provides the run-scoped
+`core/llm/tool_permissions/` provides the run-scoped
`ToolPermissionRegistry` and `ToolPermissionMiddleware`. The registry is a
**governed-tool list**, not a mandatory declaration table:
@@ -270,7 +270,7 @@ The 2.0 `Toolkit` is constructed once, in `core/llm/factory/build.py`: `Toolkit(
tools become dispatchable.
Native tools may opt in with
-`ToolCollector.register_tool_function(..., permission=spec)`. The platform's
+`ToolAssembly.append(function_tool(..., dispatch_spec=spec))`. The platform's
local-file, Bash, and My Space rules live in `builtin_tool_permission()`.
Native tools with neither an explicit spec nor a built-in entry pass through.
@@ -297,7 +297,7 @@ tools that should be governed need a registry rule.
## Registration: DB-driven config + catalog gating
-The source of truth for MCP server configuration is the `admin_mcp_servers` table (ORM: `core/db/models.py::AdminMcpServer`), read through `core/services/mcp_service.py::McpServerConfigService` with a 30-second TTL cache, in a dict format compatible with the legacy `MCP_SERVERS` (`transport / command / args / env / url / headers / is_stable`). `core/config/mcp_config.py` remains as the URL builder for the built-in servers (first-deployment seeds).
+The source of truth for MCP server configuration is the `admin_mcp_servers` table (ORM: `core/db/models/`), read through `core/capabilities/mcp/service.py::McpServerConfigService` with a 30-second TTL cache, using native client configuration (`transport / command / args / env / url / headers`). `core/config/mcp_config.py` remains as the URL builder for the built-in servers (first-deployment seeds).
Whether a server is **visible to the model** additionally passes through [catalog](catalog.md) gating: each entry in the `mcp` section of `core/config/catalog.json` corresponds to a server_id, and a server whose `is_enabled(id, "mcp_server")` is false will not be registered with the agent even if connected.
@@ -309,7 +309,7 @@ The `/config` system console's **MCP Tools → MCP Server Management** view maps
- **Probe-on-create**: `_probe_connectivity` performs a real connection; failures are rejected before persisting;
- **Toggle & ordering**: `POST /{id}/toggle` switches a server instantly (refreshing the catalog and the connection pool);
- **Secret protection**: HTTP header values are encrypted at rest and returned as `***`; secret-looking `env_vars` values are masked as well;
-- **Test & reload**: `POST /{id}/test` re-probes a single server; `POST /reload-pool` hot-rebuilds the connection pool.
+- **Test & reload**: `POST /{id}/test` re-probes a single server; `automatic cache invalidation` refreshes discovery on the next run.
- **Move to MCP Marketplace**: creates a credential-free marketplace snapshot
from an existing remote HTTP/SSE MCP and disables the original global
instance. The service then leaves the MCP server list and can be reviewed,
@@ -329,7 +329,7 @@ Regular users can add remote MCP servers visible **only to themselves** (`api/ro
- `POST /v1/me/mcp-servers`: add a private remote MCP over public HTTP or HTTPS using HTTP/SSE transports; the user entry point deliberately forbids stdio (no arbitrary command execution on the server), and DNS/IP validation blocks localhost, private, link-local, and reserved addresses; probe-on-create means unreachable endpoints are never persisted. HTTP is plaintext, so HTTPS remains recommended for production;
- `DELETE /v1/me/mcp-servers/{id}`: remove one's own private MCP.
-Implementation reuses the same `admin_mcp_servers` table: `owner_user_id` = current user for owner isolation, auto-generated `umcp_` server IDs to avoid clashes, and `is_stable=False` to keep them out of the warmup pool. HTTP header values are encrypted with Fernet at rest and decrypted only at runtime. The feature is gated by the per-user `can_add_mcp` permission flag (open by default in the single-tenant Community Edition; granted per user by organisation admins in the Enterprise Edition — see [editions](../editions/overview.md)).
+Implementation reuses the same `admin_mcp_servers` table: `owner_user_id` = current user for owner isolation, auto-generated `umcp_` server IDs to avoid clashes. HTTP header values are encrypted with Fernet at rest and decrypted only at runtime. The feature is gated by the per-user `can_add_mcp` permission flag (open by default in the single-tenant Community Edition; granted per user by organisation admins in the Enterprise Edition — see [editions](../editions/overview.md)).
## MCP marketplace
@@ -409,19 +409,19 @@ docker-compose up -d --build mcp
| `src/backend/extensions/mcp_servers/_serve.py` | Unified stdio / streamable-http entry point |
| `src/backend/extensions/mcp_servers/registry.py` | Installation placement, exposure, packages, and base ports |
| `src/backend/extensions/mcp_servers/_ports.py` | Deployment port mapping |
-| `src/backend/core/llm/mcp_pool.py` | MCP connection pool (stdio pooled / HTTP per-request) |
-| `src/backend/core/llm/mcp_manager.py` | MCPClient construction + bare tool-name restoration |
-| `src/backend/core/llm/tool_permissions.py` | Governed-tool registry, decisions, approval routing, and execution tickets |
-| `src/backend/core/services/mcp_service.py` | DB-driven server config service (30 s cache) |
-| `src/backend/core/services/mcp_marketplace_service.py` | Marketplace publishing, review, installation, visibility, and security controls |
-| `src/backend/core/services/mcp_oauth_service.py` | OAuth 2.1 login, SDK metadata discovery, encrypted token storage, and refresh |
-| `src/backend/core/services/mcp_marketplace_monitor.py` | Periodic remote tool-snapshot revalidation and drift monitoring |
+| `src/backend/core/llm/mcp/lifetime.py` | Native MCP client ownership and discovery |
+| `src/backend/core/llm/mcp/manager.py` | MCPClient construction + bare tool-name restoration |
+| `src/backend/core/llm/tool_permissions/` | Governed-tool registry, decisions, approval routing, and execution tickets |
+| `src/backend/core/capabilities/mcp/service.py` | DB-driven server config service (30 s cache) |
+| `src/backend/features/marketplace/` | Marketplace publishing, review, installation, visibility, and security controls |
+| `src/backend/core/capabilities/mcp/oauth.py` | OAuth 2.1 login, SDK metadata discovery, encrypted token storage, and refresh |
+| `src/backend/features/marketplace/` | Periodic remote tool-snapshot revalidation and drift monitoring |
| `src/backend/core/config/mcp_config.py` | Built-in server URL builder (http://mcp:NNNN/mcp/) |
| `src/backend/core/config/catalog.json` | Capability catalog: MCP enable/disable seeds |
| `src/backend/api/routes/v1/admin_mcp_servers.py` | Admin custom-MCP API |
| `src/backend/api/routes/v1/me_capabilities.py` | User self-service private MCP / skill API |
-| `src/backend/api/routes/v1/mcp_marketplace.py` | User-facing MCP marketplace API |
-| `src/backend/api/routes/v1/admin_mcp_marketplace.py` | Admin MCP marketplace and review API |
+| `src/backend/features/marketplace/api/mcp_marketplace.py` | User-facing MCP marketplace API |
+| `src/backend/features/marketplace/api/admin_mcp_marketplace.py` | Admin MCP marketplace and review API |
| `docker/Dockerfile.mcp` | mcp container image (MCP runtime, plotting dependencies, and CJK fonts) |
Related docs: [Capability catalog](catalog.md) · [Agent skills](agent-skills.md) · [Knowledge base](knowledge-base.md) · [Editions & licensing](../editions/overview.md)
diff --git a/document/en/modules/memory.md b/document/en/modules/memory.md
index 328e9364..77acffa8 100644
--- a/document/en/modules/memory.md
+++ b/document/en/modules/memory.md
@@ -68,7 +68,7 @@ save_memories_background()
· sanitizer gate → write L1/L2/L3/session → audit side channel
```
-The integration layer for retrieval and injection lives in `src/backend/orchestration/memory_integration.py`. mem0's LLM / embedder / Milvus / reranker configuration is assembled in `src/backend/core/memory/service.py`, while Neo4j graph reads and writes live in `src/backend/core/memory/graph.py`. Model configs are resolved from the DB roles `memory` / `embedding` first, falling back to environment variables.
+The integration layer for retrieval and injection lives in `src/backend/core/agent_runtime/integrations/memory.py`. mem0's LLM / embedder / Milvus / reranker configuration is assembled in `src/backend/core/memory/service.py`, while Neo4j graph reads and writes live in `src/backend/core/memory/graph.py`. Model configs are resolved from the DB roles `memory` / `embedding` first, falling back to environment variables.
## Write pipeline and extractors
@@ -140,7 +140,7 @@ Two independent toggles, both stored in `users_shadow.metadata` (ORM column `ext
| `memory_enabled` | Persistent memory **read**: inject the frozen block at session start | `false` |
| `memory_write_enabled` | **Write**: extract and save memories after each conversation | `false` |
-Project chats get their own scope: personal projects and the default workspace use the real `user_id`, while team projects use `scope_user_id = "team:"` — all team members write into the same mem0 bucket for shared recall, with the real author preserved in `metadata.author_user_id` (see `orchestration/memory_integration.py::save_memories_background` and `api/routes/v1/memories.py::list_memories`). Project-level toggles live in `projects.metadata` (`memory_enabled` / `memory_write_enabled`, defaulting to `true` inside a project); see [Projects & MySpace](./projects-myspace.md).
+Project chats get their own scope: personal projects and the default workspace use the real `user_id`, while team projects use `scope_user_id = "team:"` — all team members write into the same mem0 bucket for shared recall, with the real author preserved in `metadata.author_user_id` (see `core/agent_runtime/integrations/memory.py::save_memories_background` and `api/routes/v1/memories.py::list_memories`). Project-level toggles live in `projects.metadata` (`memory_enabled` / `memory_write_enabled`, defaulting to `true` inside a project); see [Projects & MySpace](./projects-myspace.md).
## Frontend memory center
@@ -233,8 +233,8 @@ See the [environment variable reference](../deployment/environment-variables.md)
| `src/backend/core/memory/sanitizer.py` | Sanitizer gate (hardcoded + DB-managed rules) |
| `src/backend/core/memory/audit.py` | Audit side channel (Enterprise Edition, EE) |
| `src/backend/core/memory/context.py` | `MemoryContext` and workspace / layer resolution |
-| `src/backend/orchestration/memory_integration.py` | Retrieval launch, frozen-block assembly and injection, save delegation |
-| `src/backend/orchestration/workflow.py` | Main orchestration: memory hook wiring |
+| `src/backend/core/agent_runtime/integrations/memory.py` | Retrieval launch, frozen-block assembly and injection, save delegation |
+| `src/backend/core/agent_runtime/runner.py` | Main orchestration: memory hook wiring |
| `src/backend/api/routes/v1/memories.py` | `/v1/memories` management API |
| `src/backend/core/db/models/memory.py` | `ProfileMemory`, `MemoryOutbox`, and `MemorySanitizerRule` ORM |
| `src/backend/edition_ee/db/models/memory.py` | `MemoryAudit` ORM (EE only) |
diff --git a/document/en/modules/model-providers.md b/document/en/modules/model-providers.md
index 969c7049..bea68107 100644
--- a/document/en/modules/model-providers.md
+++ b/document/en/modules/model-providers.md
@@ -9,7 +9,7 @@ In Config → Model Management (EE), use the switch in the provider list's Statu
## Configuring models: providers + roles
-Two tables (`core/db/models.py`): `model_providers` (base_url / api_key / model_name / extra_config / is_active) and `model_role_assignments` (role_key → provider_id). Roles are defined in `core/db/model_repository.py::ROLE_DEFINITIONS`:
+Two tables (`core/db/models/`): `model_providers` (base_url / api_key / model_name / extra_config / is_active) and `model_role_assignments` (role_key → provider_id). Roles are defined in `core/models/repository.py::ROLE_DEFINITIONS`:
| role_key | Purpose | Type |
|---|---|---|
@@ -36,6 +36,8 @@ Precedence is **shared subagent model → an agent's existing explicit model →
### Wire protocol: Responses by default, chat completions only as a fallback
+The pinned AgentScope version natively parses raw reasoning and summary events from Responses. The platform preserves each event's `item_id` and part index to prevent streaming failures and keep distinct reasoning items and parts separate.
+
OpenAI-compatible endpoints come in two wire protocols: the older `POST /chat/completions` and the newer `POST /responses`. The platform **defaults to Responses**, because it carries the model's reasoning as a first-class `reasoning` item that can be handed back verbatim on the next turn; `/chat/completions` has nowhere to put reasoning, so it has to be smuggled through a `reasoning_content` field. Chat completions is used only for endpoints that genuinely do not route `/responses` — commonly thin relay gateways.
The choice lives in `extra_config.api_protocol` (`responses` / `chat_completions`). It is never inferred from the vendor name: saving a provider probes the endpoint, and only an endpoint that clears both stages is recorded as Responses-capable.
@@ -124,7 +126,7 @@ Retry policy: the model layer runs with `max_retries=0`; the agent layer's `Mode
## Dynamic model switching (chat_mode)
-Each frontend message may carry `chat_mode` (`turbo / fast / medium / high / max`); before the reply starts, `core/llm/middlewares.py::DynamicModelMiddleware` (`on_reply`) hot-swaps `agent.model`:
+Each frontend message may carry `chat_mode` (`turbo / fast / medium / high / max`); before the reply starts, `core/llm/runtime/` (`on_reply`) hot-swaps `agent.model`:
```
chat_mode → hooks._resolve_chat_mode(agent.state)
@@ -134,7 +136,7 @@ chat_mode → hooks._resolve_chat_mode(agent.state)
high/max → reasoning_effort=high/max (endpoint must declare support)
```
-Legacy clients that only send `enable_thinking` are mapped to medium / fast. This is the AgentScope 2.0 incarnation of the old 1.x "pre_reply dynamic-model hook" — the hook factories were refactored into middlewares, and `core/llm/hooks.py` now only hosts pure-function helpers.
+Legacy clients that only send `enable_thinking` are mapped to medium / fast. This is the AgentScope 2.0 incarnation of the old 1.x "pre_reply dynamic-model hook" — the hook factories were refactored into middlewares, and `core/harness/hooks.py` now only hosts pure-function helpers.
## Environment variables and MCP subprocess injection
@@ -147,7 +149,7 @@ Full variable list: [Environment Variables](../deployment/environment-variables.
## Personal API keys
-Users can issue personal keys for programmatic access to the platform API (`api/routes/v1/api_keys.py` + `core/services/api_key_service.py`):
+Users can issue personal keys for programmatic access to the platform API (`api/routes/v1/api_keys.py` + `core/auth/api_keys.py`):
- Plaintext keys look like `sk-jx-` and are **returned exactly once at creation**; the DB stores only a SHA256 hash + prefix. Expiry options 7/30/90/180/365 days or never; enable/disable and revoke supported.
- Usage: `Authorization: Bearer sk-jx-...`; the auth layer (`core/auth/backend.py`) recognizes the prefix and resolves the key to a user context via `resolve_api_key`, checking enabled/unrevoked/unexpired.
@@ -157,7 +159,7 @@ Endpoints: `GET/POST /v1/me/api-keys`, `PATCH /v1/me/api-keys/{id}` (toggle), `D
## Billing and usage
-Token usage is accumulated into the end-of-stream `meta.usage` (`orchestration/streaming.py` sums `ModelCallEndEvent`s) and persisted with the message. Two console report groups sit on top (`CONFIG_TOKEN`):
+Token usage is accumulated into the end-of-stream `meta.usage` (`core/llm/runtime/streaming.py` sums `ModelCallEndEvent`s) and persisted with the message. Two console report groups sit on top (`CONFIG_TOKEN`):
- **Usage logs** `api/routes/v1/admin_usage_logs.py`: `GET /v1/admin/usage-logs` (detail), `/summary` (aggregate), `/models` (distinct model names).
- **Billing reports** `api/routes/v1/admin_billing.py` (**Enterprise Edition (EE)** — part of the full admin console): `GET /v1/admin/billing/summary` (cost aggregated by user/model), model pricing CRUD (`/pricing`, input/output unit prices, currency), `GET /v1/admin/billing/export` (CSV cost export).
@@ -166,21 +168,21 @@ Community Edition users can see their own token usage; organization-level billin
## Routing strategy (ROUTER_STRATEGY)
-`orchestration/strategy.py`: `ROUTER_STRATEGY=main_only` (default) always routes to the main agent; `llm_router` is a reserved placeholder that currently also falls back to `MainOnlyStrategy` (safe by default). Actual multi-agent dispatch happens via `@mentions` + the `call_subagent` tool — see [Chat & Agent Orchestration](chat.md).
+`core/agent_runtime/strategy.py`: `ROUTER_STRATEGY=main_only` (default) always routes to the main agent; `llm_router` is a reserved placeholder that currently also falls back to `MainOnlyStrategy` (safe by default). Actual multi-agent dispatch happens via `@mentions` + the `call_subagent` tool — see [Chat & Agent Orchestration](chat.md).
## Source map
| Topic | Path |
|---|---|
-| Model factory / JxOpenAIChatModel | `src/backend/core/llm/chat_models.py` |
-| Role resolution service (DB + cache) | `src/backend/core/services/model_config.py` |
-| Role definitions / provider repository | `src/backend/core/db/model_repository.py` |
+| Model factory / JxOpenAIChatModel | `src/backend/core/models/chat_models.py` |
+| Role resolution service (DB + cache) | `src/backend/core/models/config.py` |
+| Role definitions / provider repository | `src/backend/core/models/repository.py` |
| Management API | `src/backend/api/routes/v1/models.py` |
-| Dynamic-model middleware | `src/backend/core/llm/middlewares.py::DynamicModelMiddleware`, helpers in `core/llm/hooks.py` |
-| MCP env injection | `src/backend/core/services/model_config.py::get_mcp_env_overlay`, `core/config/runtime_env.py` |
-| Personal API keys | `src/backend/api/routes/v1/api_keys.py`, `core/services/api_key_service.py`, `core/auth/backend.py` |
+| Dynamic-model middleware | `src/backend/core/llm/runtime/`, helpers in `core/harness/hooks.py` |
+| MCP env injection | `src/backend/core/models/config.py::get_mcp_env_overlay`, `core/config/runtime_env.py` |
+| Personal API keys | `src/backend/api/routes/v1/api_keys.py`, `core/auth/api_keys.py`, `core/auth/backend.py` |
| Usage logs / billing | `src/backend/api/routes/v1/admin_usage_logs.py`, `api/routes/v1/admin_billing.py` |
-| Routing strategy | `src/backend/orchestration/strategy.py` |
+| Routing strategy | `src/backend/core/agent_runtime/strategy.py` |
| Fail-fast on missing main model | `src/backend/api/routes/v1/chats/__init__.py::_ensure_main_model_configured` |
### Child cancellation and parameter overrides
diff --git a/document/en/modules/personal-agent.md b/document/en/modules/personal-agent.md
index ae667c61..dd881621 100644
--- a/document/en/modules/personal-agent.md
+++ b/document/en/modules/personal-agent.md
@@ -217,24 +217,18 @@ When a Linux desktop runtime has no input driver, the connection offers session
Prepare Enterprise Edition, PostgreSQL, Redis, and the OpenSandbox images using the existing deployment guide.
-1. Build the computer image: `docker build -f docker/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .`. Do not overwrite a tag that a running computer uses.
+1. Build the computer image: `docker build -f docker/personal-agent/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .`. Do not overwrite a tag that a running computer uses.
2. Set HOST_STORAGE_PATH to an absolute host path.
3. Set PERSONAL_AGENT_OPENSANDBOX_API_KEY to a separate random secret of at least 32 characters.
4. Set PERSONAL_AGENT_OPENSANDBOX_DOMAIN to personal-agent-opensandbox:8080.
5. Set PERSONAL_AGENT_IMAGE to hugagent-personal-agent:v5. Existing computers use the new image when they are recreated.
-6. Run the root Compose file with docker/compose.personal-agent.yml and the personal-agent profile.
+6. Run the root Compose file with docker/personal-agent/compose.personal-agent.yml and the personal-agent profile.
7. Apply the existing database migration procedure. Apply personalagent01 through personalagent05. These migrations create module tables, backfill message links, create task sessions, and add device session operations.
The extra Compose file creates a separate controller, compute network, and private configuration volume. The controller needs Docker socket access. Do not commit credentials. The existing database encryption key protects sandbox and email credentials.
Missing Personal Agent controller settings cause a failure. The service does not fall back to the ordinary controller. The worker checks permanent computers once a minute. Pausing the task queue preserves the computer.
-## Verification
-
-Tests cover identity, concurrency, leases, approvals, revocation, history pagination, and ordinary chat restrictions. Isolated service tests also cover real models, cloud screenshots and input, persistent files, SMTP/IMAP attachments, and the frontend flow.
-
-Ethereal captures test email. It does not prove delivery to normal Internet inboxes. Mac and Windows native desktop tests require their respective systems. See the internal acceptance record for passed and pending checks.
-
Wait until the agent has replied to every message before you downgrade personalagent02. The downgrade archives task links and scoped instructions in message metadata. Upgrading again restores them.
## Status panel
@@ -262,6 +256,8 @@ Personal Agent manages the user's HugAgentOS projects, chats and runs with one t
The cloud computer includes the lightweight `hugagent` CLI. It manages existing backends.
Web clients and desktop cloud mode share `cloud`. Desktop local mode uses `device:`.
+Inside the cloud computer this client is called `hugagent`. Installed anywhere else (a `pip install` of the backend source, or the wheel built by `scripts/build-hugagent-cli.py`), the same client is the `hugagent-session` command with the same options. On a workstation, `hugagent` is the terminal client (`packages/cli`). The local server's `onboard`, `serve`, `doctor` and `engine` commands belong to `hugagent-server`; the session client only points you there when it receives one.
+
```bash
hugagent device list
hugagent --target cloud chat list
diff --git a/document/en/modules/projects-myspace.md b/document/en/modules/projects-myspace.md
index 3fb51354..2435daf7 100644
--- a/document/en/modules/projects-myspace.md
+++ b/document/en/modules/projects-myspace.md
@@ -35,7 +35,7 @@ Key ORM models (`src/backend/core/db/models/`):
## Projects
-Routes: `src/backend/api/routes/v1/projects.py` (CE router table); business logic in `core/services/project_service.py` and `project_file_service.py`.
+Routes: `src/backend/api/routes/v1/projects.py` (CE router table); business logic in `core/projects/` and `project_file_service.py`.
| Method | Path | Description |
|---|---|---|
@@ -71,16 +71,16 @@ In the project overview, the model and thinking-effort menu always opens downwar
### How project context enters a conversation
-When a conversation is started inside a project (the request carries `project_id`), `core/services/session_operations/request_context.py` assembles the workflow context:
+When a conversation is started inside a project (the request carries `project_id`), `core/chat/operations/request_context.py` assembles the workflow context:
1. Project metadata is loaded — `project_name`, `project_instructions`, the linked folder name and working directory. Instructions still come from `AGENTS.md`; cloud and local project files are not recursively enumerated;
2. `core/llm/factory/build.py` injects these into the system prompt via `_build_project_section` (`build_system_prompt(cfg, ctx=...)`);
3. The project-level memory scope takes effect: the workspace becomes `project:`; team projects use the mem0 bucket `team:` (members share memories), and the project's own `metadata.memory_enabled` / `memory_write_enabled` override the user-level toggles (defaulting to on inside projects) — see [Memory System](./memory.md);
-4. Sandbox path scoping: in project conversations, the agent's `/myspace/...` file operations are redirected under the linked folder (the explicit `ProjectScope` parameter mechanism in `core/llm/tools/myspace_vfs/__init__.py`).
+4. Sandbox path scoping: in project conversations, the agent's `/myspace/...` file operations are redirected under the linked folder (the explicit `ProjectScope` parameter mechanism in `core/files/spaces/`).
Custom project-mode prompt templates use project names, directory scope and instructions. Before upgrading an existing deployment, remove obsolete file-inventory and file-count placeholders from saved templates; the runtime no longer supplies those variables.
-Team project permissions follow team roles: owner/admin always have admin rights; members are governed by `file_permission` (editor/viewer) (`core/auth/permissions_iface.py::require_project_access`).
+Team project permissions follow team roles: owner/admin always have admin rights; members are governed by `file_permission` (editor/viewer) (`core/auth/personal_permissions_iface.py::require_project_access`).
### Project sidebar controls
@@ -167,7 +167,7 @@ Permission model: `TeamMember.role` (owner/admin/member) + `file_permission` (vi
Three complementary paths:
-1. **Attachment injection (hooks)**: frontend requests send only `file_id`, `name`, and `mime_type` in each `attachments[]` item, never the file body. `core/llm/hooks.py` lazily downloads and parses by `file_id`, caching `parsed_text` and its summary; the first turn injects only metadata and a bounded preview of at most 5,000 characters, while xlsx previews also include total dimensions, headers, and a few data rows. The model reads content beyond the preview on demand through `read_artifact`; images still use multimodal injection. Every read validates `user_id` ownership to block forged cross-user access;
+1. **Attachment injection (hooks)**: frontend requests send only `file_id`, `name`, and `mime_type` in each `attachments[]` item, never the file body. `core/harness/hooks.py` lazily downloads and parses by `file_id`, caching `parsed_text` and its summary; the first turn injects only metadata and a bounded preview of at most 5,000 characters, while xlsx previews also include total dimensions, headers, and a few data rows. The model reads content beyond the preview on demand through `read_artifact`; images still use multimodal injection. Every read validates `user_id` ownership to block forged cross-user access;
2. **Project file access**: a project conversation's system prompt supplies project instructions and directory scope without an automatic file inventory or count. The agent discovers files on demand through directory, search and read tools;
3. **Sandbox virtual filesystem**: with code execution enabled, the `/myspace/...` path maps MySpace into the sandbox (lazy loading + reverse sync); team projects map the team folder — see [Sandbox](./sandbox.md).
@@ -176,8 +176,8 @@ Three complementary paths:
| Path | Responsibility |
|---|---|
| `src/backend/api/routes/v1/projects.py` | Projects API |
-| `src/backend/core/services/project_service.py` / `project_file_service.py` | Project business logic |
-| `src/backend/core/services/project_scope.py` | `ProjectScope` (sandbox path scoping) |
+| `src/backend/core/projects/` / `project_file_service.py` | Project business logic |
+| `src/backend/core/projects/personal_scope.py` | `ProjectScope` (sandbox path scoping) |
| `src/backend/api/routes/v1/myspace_folders.py` | Personal folders API |
| `src/backend/api/routes/v1/artifacts.py` | Asset list / chat favorites / add-to-KB |
| `src/backend/edition_ee/routes/team_files.py` | Team folders & files API (Enterprise Edition, EE) |
@@ -186,9 +186,9 @@ Three complementary paths:
| `src/backend/core/db/models/identity.py` | Shared identity ORM such as `UserFolder` |
| `src/backend/edition_ee/db/models/identity.py` | `Team` / `TeamMember` / `TeamFolder` ORM (EE only) |
| `src/backend/core/db/models/artifact.py` | `Artifact` ORM |
-| `src/backend/core/llm/hooks.py` | Attachment context injection (`_build_file_context`, etc.) |
+| `src/backend/core/harness/hooks.py` | Attachment context injection (`_build_file_context`, etc.) |
| `src/backend/core/llm/factory/build.py` | Project section injection into the system prompt |
-| `src/backend/core/llm/tools/myspace_vfs/__init__.py` | MySpace ↔ sandbox mapping layer |
+| `src/backend/core/files/spaces/` | MySpace ↔ sandbox mapping layer |
| `src/frontend/src/components/projects/` | Project frontend components |
| `src/frontend/src/components/sidebar/Sidebar.tsx` | Sidebar project groups, quick chat creation, and project actions |
| `src/frontend/src/components/myspace/` | MySpace frontend components |
@@ -262,11 +262,8 @@ staleness is allowed. Failed registrations, missing files and failed downloads a
revisited at the next normal synchronization, without continuous background retries.
Before upgrading, stop writers and back up the database and myspace_cache.
-Preview with `python src/backend/scripts/clean_myspace_duplicates.py --all`.
-Apply only with `--apply --backup-dir `.
-Cleanup deletes every live record in each duplicate group and its mirror path;
-it does not retain the newest record. Tombstones prevent historical backfill from
-resurrecting deleted identities. Original objects and recovery journals are retained.
+Inspect duplicate names and review a cleanup and recovery plan before upgrading.
+There is no standalone deduplication script.
Then upgrade Alembic (EE: personalname01; CE: ce_0018), resume services and run
reconciliation and end-to-end checks. Migration refuses remaining duplicates
instead of silently deleting data in another environment.
@@ -275,13 +272,13 @@ never restore duplicate records into the running service.
### Space file tools
-Space file tools share the `core/llm/tools/space_tools.py` registration entry point: `space_list_myspace_files`, `space_stage_myspace_file`, `space_create_folder`, `space_move`, and `space_delete`. EE also provides `space_list_team_files`. Generic Read/Write/Edit/Glob/Grep names remain unchanged.
+Space file tools share the `core/files/spaces/` registration entry point: `space_list_myspace_files`, `space_stage_myspace_file`, `space_create_folder`, `space_move`, and `space_delete`. EE also provides `space_list_team_files`. Generic Read/Write/Edit/Glob/Grep names remain unchanged.
Favorite-chat tools have been removed; historical chats remain accessible through `list_related_chats` and `read_chat`.
### Unified mounted-space synchronization
-Personal listeners live in `core/space_sync/`; team adapters live in `edition_ee/services/space_sync/`. Callbacks enqueue in memory, workers batch a durable SQLite journal outside writable mounts, then update database metadata and object storage with authorization and version checks. Native moves retain file/folder IDs. Empty directories, deletion, restart recovery and parsed-cache invalidation share this pipeline. Committed database changes refresh registered mounts; rollback publishes nothing. Conflicts preserve local bytes and fail synchronization barriers. Network transfers and human approval are outside callback latency; measure complete synchronization in the actual deployment.
+Personal listeners live in `core/files/spaces/`; team adapters live in `edition_ee/services/workspace_adapter.py`. Callbacks enqueue in memory, workers batch a durable SQLite journal outside writable mounts, then update database metadata and object storage with authorization and version checks. Native moves retain file/folder IDs. Empty directories, deletion, restart recovery and parsed-cache invalidation share this pipeline. Committed database changes refresh registered mounts; rollback publishes nothing. Conflicts preserve local bytes and fail synchronization barriers. Network transfers and human approval are outside callback latency; measure complete synchronization in the actual deployment.
### Project environment
@@ -291,7 +288,7 @@ Custom environments bind subagents, plugins, skills and connectors. The main age
The backend limits capability registration, explicit invocation, restored session capabilities, subagents and dynamic plugin loading. An empty custom environment never falls back to all resources. Changes apply from the next turn; running responses and their derived jobs keep the original configuration. Conversations outside the project are unaffected. Basic file and conversation tools retain their existing permission rules.
-Implementation follows separate responsibilities: `core/project_environment/` validates configuration and resolves dependencies without catalog access. The resource service reads authorized inventory, the environment service saves and previews configuration, and the runtime policy freezes and narrows registration and invocation. The frontend editor composes the interface; `useProjectEnvironment` loads resources, previews dependencies and handles marketplace acquisition. Resource pickers display explicit bindings and inferred dependencies.
+Implementation follows separate responsibilities: `core/projects/environment/` validates configuration and resolves dependencies without catalog access. The resource service reads authorized inventory, the environment service saves and previews configuration, and the runtime policy freezes and narrows registration and invocation. The frontend editor composes the interface; `useProjectEnvironment` loads resources, previews dependencies and handles marketplace acquisition. Resource pickers display explicit bindings and inferred dependencies.
### Online cloud document editing
diff --git a/document/en/modules/prompts.md b/document/en/modules/prompts.md
index 6277e574..752eb6da 100644
--- a/document/en/modules/prompts.md
+++ b/document/en/modules/prompts.md
@@ -30,7 +30,7 @@ src/backend/prompts/prompt_text/default/system/
└── 40_format.system.md # output format + [ref:tool-N] citation contract
```
-After the base prompt, runtime appends dynamic sections per context: the tools & skills notice (`_TOOLS_AND_SKILLS_NOTICE`), the lightweight KB catalog (`prompts/kb_lite_section.py`), the project-mode section (`prompts/project_section.py`, injected only for project chats), the code-execution segment and batch-mode hint (appended by `factory/build.py`), and the sub-agent routing table (`core/llm/subagent_tool.py::build_subagent_prompt_section`).
+After the base prompt, runtime appends dynamic sections per context: the tools & skills notice (`_TOOLS_AND_SKILLS_NOTICE`), the lightweight KB catalog (`prompts/kb_lite_section.py`), the project-mode section (`prompts/project_section.py`, injected only for project chats), the code-execution segment and batch-mode hint (appended by `factory/build.py`), and the sub-agent routing table (`core/llm/subagents/tool.py::build_subagent_prompt_section`).
### Caching
@@ -49,7 +49,7 @@ Pool management and runtime assembly share this read path. After an active versi
### Execution manifest and runtime binding
-Before an Agent can execute, `core/llm/execution_manifest.py::PromptManifestBuilder` records the exact final request surface. Every ordered prompt section records `id`, origin, trust level, priority, cache class, budget, token estimate, content hash and version. Every final tool definition similarly hashes its name, complete description, canonical parameter schema, permission policy and recovery policy. Tool definitions are sorted by stable id; prompt sections retain their deliberate prefix order.
+Before an Agent can execute, `core/harness/execution_manifest.py::PromptManifestBuilder` records the exact final request surface. Every ordered prompt section records `id`, origin, trust level, priority, cache class, budget, token estimate, content hash and version. Every final tool definition similarly hashes its name, complete description, canonical parameter schema, permission policy and recovery policy. Tool definitions are sorted by stable id; prompt sections retain their deliberate prefix order.
The builder emits `prompt_hash`, `prompt_manifest_hash`, `tool_manifest_hash`, `context_hash` and one `aggregate_hash`. Manifest construction and AgentScope model input consume the same run-scoped frozen tool/skill snapshot, so the recorded surface cannot differ from the request. Progressive plugin activation invalidates that snapshot and publishes an explicit next `surface_generation` before the next model call.
@@ -59,15 +59,15 @@ Prompt text, project instructions, file names, tool descriptions and schemas are
### Canonical Context IR
-Immediately before every model call, `core/llm/context_ir.py` converts the complete request context into immutable `ContextItem` records. User input, assistant history, account identity, frozen memory, project/file material, attachments, tool calls/results, compaction summaries, steer messages and harness reminders therefore carry machine-readable `kind`, `origin`, `trust`, visibility, priority, item budget, truncation policy, content reference/hash, cache class and creation sequence. Compatibility rendering may still use a `user` role for memory and mid-turn reminders, but their provenance is no longer inferred from XML tags or from that role.
+Immediately before every model call, `core/llm/context/ir.py` converts the complete request context into immutable `ContextItem` records. User input, assistant history, account identity, frozen memory, project/file material, attachments, tool calls/results, compaction summaries, steer messages and harness reminders therefore carry machine-readable `kind`, `origin`, `trust`, visibility, priority, item budget, truncation policy, content reference/hash, cache class and creation sequence. Compatibility rendering may still use a `user` role for memory and mid-turn reminders, but their provenance is no longer inferred from XML tags or from that role.
`ContextAssembler` applies a deterministic order and an explicit message budget: the model window minus a 15% response reserve and the canonical final-tool-schema reserve. Critical system/current-user items cannot be silently displaced; optional items are selected by priority; text and tool outputs use declared head/tail policies; and every tool call/result pair is kept, shortened or excluded as one unit. The sanitized context manifest explains every inclusion, truncation and exclusion without retaining plaintext. Its hash is folded into the request's `context_hash` and `aggregate_hash`, then rebound to the run's Asset Bundle.
-`core/llm/context_adapter.py::AgentScopeContextAdapter` is the sole Context IR ↔ AgentScope message conversion seam. Persisted session metadata survives history replay, while final model roles and SSE behavior remain compatible. `ManifestBoundAgent` performs final assembly through the public `Agent.reply` path after the run-scoped tool/skill surface has been frozen.
+`core/llm/context/adapter.py::AgentScopeContextAdapter` is the sole Context IR ↔ AgentScope message conversion seam. Persisted session metadata survives history replay, while final model roles and SSE behavior remain compatible. `ManifestBoundAgent` performs final assembly through the public `Agent.reply` path after the run-scoped tool/skill surface has been frozen.
## Prompt version pool (prompt_versions)
-The pool stores multiple prompt sets in a single `ContentBlock(id="prompt_versions")` row with payload `{active: {kind: version_id}, versions: [...]}`; the service layer is `core/services/prompt_version_service.py`:
+The pool stores multiple prompt sets in a single `ContentBlock(id="prompt_versions")` row with payload `{active: {kind: version_id}, versions: [...]}`; the service layer is `core/llm/prompt_versions.py`:
- **Six kinds** (`VALID_KINDS`): `system` (main agent), `code_exec` (code-execution segment), `distillation` (skill distillation), `plan_mode` (plan mode), `subagents` (independent prompts for the platform-default Explorer, Worker, and Reviewer), and `turbo` (the standalone turbo-mode prompt).
- Each version carries `(kind, id, name, description, parts[])`, a part being `{part_id, display_name, content, sort_order, is_enabled}`.
@@ -100,8 +100,8 @@ their own enablement state.
| kind | Runtime consumer | Resolution order |
|---|---|---|
| `code_exec` | `factory/build.py` appends this segment (the code-capability prompt) to the system prompt when `CODE_CAPABILITY_ENABLED=true`; the single source of truth is `prompt_version_service.render_code_capability_segment()`, also used by the console preview | active DB version → `prompts/prompt_text/code_exec/system/*.system.md` |
-| `distillation` | Skill distillation (`core/llm/skill_distiller.py`, distilling conversation trajectories into reusable skills) | active DB version → `prompts/prompt_text/distillation/skill_distiller.system.md` |
-| `plan_mode` | The plan-generation sub-agent (`orchestration/subagents/plan_mode.py::_load_plan_prompt`) | active `plan_mode` DB version → legacy `system/90_plan_mode` part → `prompts/prompt_text/plan_mode/plan_mode.system.md` → hardcoded fallback |
+| `distillation` | Skill distillation (`features/distillation/`, distilling conversation trajectories into reusable skills) | active DB version → `prompts/prompt_text/distillation/skill_distiller.system.md` |
+| `plan_mode` | The plan-generation sub-agent (`orchestration/plans/`) | active `plan_mode` DB version → legacy `system/90_plan_mode` part → `prompts/prompt_text/plan_mode/plan_mode.system.md` → hardcoded fallback |
| `turbo` | Turbo mode (when the chat-mode picker is set to Turbo, `factory/build.py` **replaces** the assembled main system prompt with this one). The tool set is admin-configurable via Config "System Config → Turbo" (`turbo.mcp_server_ids`, defaulting to internet search / web fetch / KB retrieval, **independent of catalog enable/disable state**); explicitly summoned skills / @sub-agents / plugins may join per turn (`turbo.manual_invoke_enable`); the react-iteration cap is configurable (`turbo.max_iters`, default 4). The single source of truth is `prompt_version_service.render_turbo_system_prompt()` | active `turbo` DB version → `prompts/prompt_text/turbo/turbo.system.md` → hardcoded fallback |
Sub-agents do not use the full pool assembly: `prompt_runtime.py::build_subagent_system_prompt()` builds around the user-defined `system_prompt`, reusing the `20_tools_policy` / `65_citations` / `60_format` parts from the active version (or files). See [Chat & Agent Orchestration](chat.md).
@@ -126,18 +126,6 @@ Prompts live only in the database and never ship with code; migration across env
Both accept `ADMIN_TOKEN` or `CONFIG_TOKEN`. Snapshots are validated against `PROMPT_BLOCK_MAP`, so docs snapshots and prompt snapshots cannot be imported through the wrong endpoint.
-### Scripts (src/backend/scripts/)
-
-```bash
-# Export (via a running backend API; --database-url for direct DB access also works)
-python scripts/export_content.py --api-url http://localhost:3000/api --only prompts
-# → scripts/exported/prompts_snapshot_.json
-
-# Import into the target environment (use the target machine's ADMIN_TOKEN)
-python scripts/import_content.py --api-url http:///api --prompts prompts_snapshot_.json
-# supports --no-overwrite / --dry-run
-```
-
The same flow works for offline production (image-pack delivery, persistent DB volume): copy the snapshot in alongside the image pack and `curl -X POST .../v1/content/prompts/import` inside the backend container — no restart required. When migrating to a differently-branded environment, review the snapshot manually and rewrite brand-specific wording in context before importing (never do mechanical find-and-replace).
## Source map
@@ -147,10 +135,9 @@ The same flow works for offline production (image-pack delivery, persistent DB v
| Runtime assembly + caching | `src/backend/prompts/prompt_runtime.py` |
| Providers (filesystem/inline/minimal) | `src/backend/prompts/provider.py` |
| Config (provider/parts/PROMPT_DIR) | `src/backend/prompts/prompt_config.py`, `prompts/config/default.json` |
-| Version pool service | `src/backend/core/services/prompt_version_service.py` |
+| Version pool service | `src/backend/core/llm/prompt_versions.py` |
| Console routes | `src/backend/api/routes/v1/admin_prompts.py` |
-| Migration endpoints (export/import) | `src/backend/api/routes/v1/content.py`, `core/content/content_blocks.py` |
-| Migration scripts | `src/backend/scripts/export_content.py`, `scripts/import_content.py` |
+| Migration endpoints (export/import) | `src/backend/api/routes/v1/content.py`, `core/files/content/content_blocks.py` |
| System prompt fallback files | `src/backend/prompts/prompt_text/default/system/` |
| Scenario prompt fallbacks | `src/backend/prompts/prompt_text/{code_exec,distillation,plan_mode,turbo}/` |
| Dynamic sections | `src/backend/prompts/kb_lite_section.py`, `prompts/project_section.py` |
diff --git a/document/en/modules/sandbox.md b/document/en/modules/sandbox.md
index c0921ff9..640e625f 100644
--- a/document/en/modules/sandbox.md
+++ b/document/en/modules/sandbox.md
@@ -65,7 +65,7 @@ The runtime retains at most 64 process records and only evicts completed records
The sandbox identity is resolved by `resolve_sandbox_session(sandbox_session_id, chat_id)`: a non-empty explicit ID wins, while a missing or empty value falls back to `chat_id`. The main agent, plan execution, batch items, and every subagent therefore use one conversation sandbox, and a child run never destroys it when it finishes.
-**MySpace real-time registration**: a file written under `/myspace` **is** a file in the user's My Space, and both sides must show the same state at any moment. Registration is driven by **the filesystem itself** (`core/myspace/watcher.py`): any write or delete under the mirror directory `myspace_cache/{uid}/` is registered back into the artifact ledger, **regardless of who wrote it**.
+**MySpace real-time registration**: a file written under `/myspace` **is** a file in the user's My Space, and both sides must show the same state at any moment. Registration is driven by **the filesystem itself** (`core/files/spaces/`): any write or delete under the mirror directory `myspace_cache/{uid}/` is registered back into the artifact ledger, **regardless of who wrote it**.
The earlier design had each writing tool register its own change (write / edit / file delete-move-mkdir / a before-and-after directory snapshot around `Bash`). That assumes changes can only arrive through those entry points, and they cannot: a `nohup`-ed background process writes after the command returns, sub-agents and batch runs write from another coroutine, skill CLIs write directly, and so do MCP servers. Every entry point left out shows up the same way — the file sits on the user's disk, is invisible and undeletable in the UI, and every new sandbox mounts that directory again, which is where "leftover intermediate files from the previous session" came from.
@@ -82,13 +82,11 @@ Registration splits by the nature of the file — it is **not** a blanket push:
Other points:
-- **Registration attaches no chat, and pins no workspace card**: MySpace is one per-user directory that every session's sandbox mounts, so a filesystem event carries a path and no chat identity — who wrote it is simply not knowable here. Registration therefore records only the owning user (`Artifact.chat_id` stays null), and the registry never pins: a card can only be pinned by the run that wrote the file (the model calling `pin_to_workspace`, or that run's own Write / delivery guard — all through the `core.llm.workspace` ContextVar), where both "which chat" and "which file" are certain. Picking "whichever run of this user happens to be live" instead used to file one chat's output under another chat and surface it in that chat's workspace.
+- **Registration attaches no chat, and pins no workspace card**: MySpace is one per-user directory that every session's sandbox mounts, so a filesystem event carries a path and no chat identity — who wrote it is simply not knowable here. Registration therefore records only the owning user (`Artifact.chat_id` stays null), and the registry never pins: a card can only be pinned by the run that wrote the file (the model calling `pin_to_workspace`, or that run's own Write / delivery guard — all through the `core.llm.runtime.workspace` ContextVar), where both "which chat" and "which file" are certain. Picking "whichever run of this user happens to be live" instead used to file one chat's output under another chat and surface it in that chat's workspace.
- **The reference point is the artifact record, not the mirror cache**: with the bind mount on, the sandbox's `/workspace/myspace/{uid}` *is* the backend's `myspace_cache/{uid}`, so "sandbox file matches mirror cache" is trivially true and can no longer mean "already registered". That mismatch used to make write-back a permanent no-op.
- **"Newer on disk than in the ledger" is the only signal**: when the backend writes into the mirror itself (materialising a UI upload, restoring a declined change) it aligns the file's mtime to the registration time, so the watcher never mistakes the backend's own write for a fresh sandbox change — no separate "I wrote this" bookkeeping is needed.
- **Every worker watches; a claim deduplicates**: the confirmation bar lives in the process running that chat, so the watcher has to run in every worker (the cost is inotify watches scaling as workers × directories against `fs.inotify.max_user_watches`; raise it on deployments with very many folders — exceeding it drops events **silently**); one change is still handled once, via the claim in `core.infra.ephemeral`, and preferably by the worker hosting that user's chat (only it can raise the bar).
-- **When the sandbox directory is not on this host** (`script_runner` / `cube`: the session workspace lives inside a container, or the whole sandbox is remote), `core/myspace/sandbox_sync.py` copies the sandbox's current state into the mirror directory after the command, and everything downstream is identical.
-
-The backlog that accumulated before this shipped is a **one-off data migration**: run `scripts/reconcile_myspace_mirror.py` by hand once (`--dry-run` first; add `--prune-stale` to clear leftover copies). It is deliberately not a scan that runs on every startup — that would be wasted work, and it would paper over real gaps in the live path.
+- **When the sandbox directory is not on this host** (`script_runner` / `cube`: the session workspace lives inside a container, or the whole sandbox is remote), `core/files/spaces/sandbox.py` copies the sandbox's current state into the mirror directory after the command, and everything downstream is identical.
### Large artifact delivery
@@ -185,8 +183,8 @@ fix on both execution surfaces. Updating only the cloud does not fix old clients
## Administrator sandbox management
-- **Read-only monitoring (security console)**: `api/routes/v1/config_security.py` exposes `/v1/config/security/sandbox/*` — overview, instance list, per-instance detail, snapshot list, rebuild history and effective configuration; everything goes through the providers' `admin_*` interfaces, with the UI trimmed by each provider's `admin_capabilities()` declaration (script_runner can't enumerate instances, so those columns are greyed out).
-- **Dependency rebuild (Enterprise EE)**: `api/routes/v1/admin_sandbox.py` (`/v1/admin/sandbox/*`) aggregates the pip/apt dependencies declared by all skills (`core/services/skill_deps_aggregator.py`) and lets an admin trigger a sandbox image rebuild with one click: the `script-runner` / `opensandbox` targets run a local `docker compose build`, while the `cube` target rebuilds the template on the remote node over SSH and hot-swaps it (`core/services/sandbox_rebuild_service.py` + `cube_template_builder`), with per-run status and logs. New skill dependencies get baked into the images without hand-editing Dockerfiles.
+- **Read-only monitoring (security console)**: `features/security_console/api/config_security.py` exposes `/v1/config/security/sandbox/*` — overview, instance list, per-instance detail, snapshot list, rebuild history and effective configuration; everything goes through the providers' `admin_*` interfaces, with the UI trimmed by each provider's `admin_capabilities()` declaration (script_runner can't enumerate instances, so those columns are greyed out).
+- **Dependency rebuild (Enterprise EE)**: `features/sandbox_ops/api/admin_sandbox.py` (`/v1/admin/sandbox/*`) aggregates the pip/apt dependencies declared by all skills (`core/capabilities/skills/deps/aggregator.py`) and lets an admin trigger a sandbox image rebuild with one click: the `script-runner` / `opensandbox` targets run a local `docker compose build`, while the `cube` target rebuilds the template on the remote node over SSH and hot-swaps it (`features/sandbox_ops/rebuild.py` + `cube_template_builder`), with per-run status and logs. New skill dependencies get baked into the images without hand-editing Dockerfiles.
## Key environment variables
@@ -220,17 +218,17 @@ Full list in the [environment variable reference](../deployment/environment-vari
| `src/backend/core/sandbox/factory.py` | Explicit provider selection; initialization errors propagate |
| `src/backend/core/sandbox/providers/script_runner/provider.py` | Lightweight sandbox (CE) |
| `src/backend/core/sandbox/providers/opensandbox/provider.py` | Shared OpenSandbox provider |
-| `src/backend/core/sandbox/providers/opensandbox/session.py` | Sessions / snapshots / park-restore workers (EE) |
+| `src/backend/core/sandbox/providers/opensandbox/` | Sessions / snapshots / park-restore workers (EE) |
| `src/backend/core/sandbox/providers/opensandbox/execution.py` | Execution path + idle reaper (EE) |
-| `src/backend/core/sandbox/providers/opensandbox/internals.py` | Volume builders, metadata, user pool (EE) |
+| `src/backend/core/sandbox/providers/opensandbox/` | Volume builders, metadata, user pool (EE) |
| `src/backend/core/sandbox/providers/opensandbox/pool.py` | Two-bucket warm pool |
| `src/backend/core/sandbox/providers/cube/provider.py` | Cube remote-MicroVM provider (EE) |
| `src/backend/core/llm/tools/sandbox_tool.py` | Bash / write_stdin / sandbox_put_artifact / sandbox_get_artifact |
-| `src/backend/core/llm/offloader.py` | Overflow offloading to the session workspace |
-| `src/backend/api/routes/v1/admin_sandbox.py` | Dependency-rebuild admin API (EE) |
-| `src/backend/api/routes/v1/config_security.py` | Security-console read-only sandbox views |
-| `src/backend/core/services/sandbox_rebuild_service.py` | Image/template rebuild orchestration (EE) |
-| `docker/Dockerfile.script-runner` / `docker/Dockerfile.opensandbox` / `docker/Dockerfile.cube-sandbox` | The three sandbox images |
+| `src/backend/core/llm/context/offloader.py` | Overflow offloading to the session workspace |
+| `src/backend/features/sandbox_ops/api/admin_sandbox.py` | Dependency-rebuild admin API (EE) |
+| `src/backend/features/security_console/api/config_security.py` | Security-console read-only sandbox views |
+| `src/backend/features/sandbox_ops/rebuild.py` | Image/template rebuild orchestration (EE) |
+| `docker/sandbox/Dockerfile.script-runner` / `docker/sandbox/Dockerfile.opensandbox` / `docker/sandbox/Dockerfile.cube-sandbox` | The three sandbox images |
Related docs: [Agent skills](agent-skills.md) · [MCP tool system](mcp-tools.md) · [Projects & My Space](projects-myspace.md) · [Editions & licensing](../editions/overview.md)
@@ -258,7 +256,7 @@ The backend collects status and logs and keeps the sandbox active. The model can
The command ledger stores bounded logs, execution leases and completion delivery state. OpenSandbox recovery uses the original execution identity, sandbox binding and committed log cursor. It never restarts the command. Other providers stop owned processes on orderly shutdown. After an unexpected restart, an unrecoverable execution is reported as `unknown`.
-Migration `bashbackground01` creates the command ledger. Apply it through the normal release procedure before using the feature. Tests use isolated databases and disposable sandbox sessions. See the [architecture decision](../../internal/adr/20261018-background-bash-completion.md) and [validation record](../../internal/background-bash-validation-20261018.md).
+Migration `bashbackground01` creates the command ledger. Apply it through the normal release procedure before using the feature. Tests use isolated databases and disposable sandbox sessions.
### Reclaim accounting and multi-worker administration
@@ -273,5 +271,31 @@ OpenSandbox instance lists and counts use the controller's current running
inventory instead of cached process handles. Controller query failures mean
the count is unavailable, never a successful zero. Pool diagnostics include
scope=worker and worker_pid. They describe one backend process and are not a
-global per-user quota. This fix does not convert local pool limits into
-cross-process admission limits.
+global per-user quota. Controller admission now enforces the shared ceiling described below.
+
+## Shared capacity and maintenance ownership
+
+The controller now enforces OPENSANDBOX_POOL_MAX_TOTAL against Docker inventory
+across backend workers, warm instances, restored sessions and computer instances.
+Worker pool counts remain local diagnostics. Container creation holds a file lock
+on the controller data volume; mount initialization stays outside that critical
+section. Capacity exhaustion or a competing admission returns 429 for later retry.
+Processes of the same controller must share its opensandbox_data volume.
+
+The leader discovers idle sessions from shared bindings, including sessions
+created by other workers or surviving a restart. Identity-checked cleanup of a
+missing binding does not block other sessions. Snapshot work stops with leadership;
+GC deletes metadata only for the snapshot_id it processed.
+
+Output reads no longer prepare mounts or write object directory markers.
+Directory initialization happens during creation, while execution and file access
+still check authorization. An uncertain or timed-out health probe preserves the
+FUSE helper; only confirmed missing or disconnected mounts permit replacement.
+Command observation retries are bounded before durable recovery takes over.
+Processes without a recoverable reference require confirmed termination, not loss
+of their control handle merely because observation failed.
+
+Roll out both backend and OpenSandbox controller images. Updating only the backend
+does not activate the shared capacity limit.
+
+Skill mounts require HOST_STORAGE_PATH to identify the host directory corresponding to backend STORAGE_PATH. Missing configuration fails explicitly; source-tree fallback is removed. Restart adoption requires explicit ownership metadata and never infers ownership from old entrypoints.
diff --git a/document/en/modules/sites.md b/document/en/modules/sites.md
index 92ae7e65..6cf07e80 100644
--- a/document/en/modules/sites.md
+++ b/document/en/modules/sites.md
@@ -66,13 +66,13 @@ The platform counts HTML page views per site (asset files excluded), shown on th
| Part | Location |
|---|---|
| Publish and lookup tools | `src/backend/extensions/mcp_servers/site_publish_mcp/` (`publish_site` / `list_sites`, shipped by the Sites plugin) |
-| Internal endpoints | `src/backend/api/routes/v1/internal_sites.py` (`/v1/internal/sites/publish`, `/v1/internal/sites/list`) |
-| Site lookup service | `src/backend/core/services/site_listing.py` (one record shape for local and cloud) |
-| Business service | `src/backend/core/services/site_service.py` |
-| Public hosting route | `src/backend/api/routes/sites_serve.py` (`GET /site/{slug}/{path}`) |
-| Management API | `src/backend/api/routes/v1/sites.py` (`/v1/sites`) |
-| Access password | `src/backend/core/services/site_password.py` (hash + credential), `src/backend/api/routes/site_gate.py` (unified password page) |
-| Database table | `sites` (`core/db/models/site.py`) |
+| Internal endpoints | `src/backend/features/sites/api/internal_sites.py` (`/v1/internal/sites/publish`, `/v1/internal/sites/list`) |
+| Site lookup service | `src/backend/features/sites/listing.py` (one record shape for local and cloud) |
+| Business service | `src/backend/features/sites/management.py` |
+| Public hosting route | `src/backend/features/sites/api/sites_serve.py` (`GET /site/{slug}/{path}`) |
+| Management API | `src/backend/features/sites/api/sites.py` (`/v1/sites`) |
+| Access password | `src/backend/features/sites/` (hash + credential), `src/backend/features/sites/api/site_gate.py` (unified password page) |
+| Database table | `sites` (`features/sites/models.py`) |
| Frontend management panel | `src/frontend/src/components/sites/SitesPanel.tsx` (Lab → Sites), `SitePasswordField.tsx` (password management) |
| nginx forwarding | `location /site/` in `src/frontend/default.conf.template` |
diff --git a/document/en/modules/storage.md b/document/en/modules/storage.md
index ab669079..947ea06c 100644
--- a/document/en/modules/storage.md
+++ b/document/en/modules/storage.md
@@ -4,7 +4,7 @@ Cloud Docker deployments in CE and EE default to S3-compatible storage. Desktop
Set STORAGE_TYPE=s3 and configure S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_ACCESS_KEY and S3_SECRET_KEY. The backend and sandbox controller must reach the endpoint.
-The optional docker/compose.object-storage.yml starts an independent S3 service. It uses its own volume and does not publish host ports. It does not share Milvus storage.
+The optional docker/object-storage/compose.object-storage.yml starts an independent S3 service. It uses its own volume and does not publish host ports. It does not share Milvus storage.
CE initialization generates separate random access and secret keys. Run python3 scripts/configure-ce-docker.py in a new CE checkout. It refuses to overwrite an existing .env.
@@ -12,7 +12,7 @@ Build the backend image before starting the bucket initializer. Object mounts us
Existing installations must back up configuration, databases and file references before changing providers. Copy required objects and verify their SHA-256 values. Preserve the source objects.
-The storage factory is core/storage/factory.py. S3StorageBackend uses boto3 and includes directory operations. LocalStorageBackend serves desktop local files. OSS and OBS adapters remain EE providers.
+The storage factory is core/files/storage/factory.py. S3StorageBackend uses boto3 and includes directory operations. LocalStorageBackend serves desktop local files. OSS and OBS adapters remain EE providers.
The artifact store supports S3, OSS and OBS. It keeps local metadata records and mirrors them into object storage. Historical download references remain valid when migrated objects keep their storage keys.
diff --git a/document/en/modules/workflow-jobs.md b/document/en/modules/workflow-jobs.md
index 40d7f5e9..69f867b6 100644
--- a/document/en/modules/workflow-jobs.md
+++ b/document/en/modules/workflow-jobs.md
@@ -23,11 +23,3 @@ Omitting `tools` inherits the parent's MCPs. A non-empty explicit list selects i
Derived instances use the main-agent iteration policy without lightweight or subagent turn caps. Each complete task executes once. Invalid output, execution failure or an uncertain callback result never automatically repeats potentially completed side effects. Inspect existing actions before explicitly resuming.
Windows local jobs use `.job//` to reduce directory depth and keep packaged Python job SDK imports below the traditional path-length limit. Each execution retains its complete unique attempt ID; resume and cancellation use the persisted actual directory and exact process reference.
-
-The native desktop end-to-end acceptance script is `src/backend/tests/integration/job_agent_local_smoke.py`. Run it with the packaged private Python and a new isolated output directory. It exercises real HTTP callbacks, managed job processes, concurrent derived main agents, child agents, Bash and file results. Only model decisions are supplied by a local scripted service. It uses no real accounts or data and does not replace installer or WebView2 window acceptance.
-
-## Validation
-
-Unit and regression tests live under src/backend/tests/orchestration/test_job*.py. The opt-in real-sandbox test, src/backend/tests/integration/job_managed_smoke.py, requires an existing job for sandbox context. It creates synthetic jobs and a temporary callback endpoint to test concurrent ledger writes, thread exit, isolated cancellation, cross-process adoption, resume, and startup failure. It does not call a model or rerun the original business script.
-
-Before deployment, verify the actual source directory mounted into the container. The working tree and running service may use different checkouts; restarting a container with the wrong mount will not apply these changes.
diff --git a/document/zh-CN/README.md b/document/zh-CN/README.md
index 4d737be3..dd1765e7 100644
--- a/document/zh-CN/README.md
+++ b/document/zh-CN/README.md
@@ -1,60 +1,95 @@
# HugAgentOS 中文文档
-> 最后更新:2026-07-21 | [English](../en/README.md)
+> 最后更新:2026-10-23 | [English](../en/README.md)
-HugAgentOS 是一个企业级 AI Agent 平台:FastAPI 后端 + React 前端 + AgentScope 2.0 智能体底座,全栈 Docker 化部署,以 open-core 模式提供**开源社区版(CE)**与**商业版(EE)**。本目录是面向部署者、使用者与二次开发者的完整产品文档。
+HugAgentOS 是一个开源、可自托管的 Agent Harness:你提出的问题在云端持续执行,关掉电脑也不会中断;
+在此之上提供 7×24 小时在线的 Personal Agent,并以 HugAgentOS Cloud(服务器)和 HugAgentOS Local
+(桌面本机)两种形态运行。平台以开源社区版(CE)与商业版(EE)双形态发行。本目录是面向部署者、
+使用者与二次开发者的完整产品文档。
## 快速上手
| 文档 | 说明 |
|------|------|
-| [产品简介](getting-started/introduction.md) | 是什么、核心特性总览、CE/EE 一览、技术栈与整体架构 |
-| [快速开始](getting-started/quick-start.md) | 选择一键安装或 Docker Compose,快速运行 HugAgentOS |
+| [产品简介](getting-started/introduction.md) | Durable Agent、运行形态、能力总览、技术栈与整体架构 |
+| [快速开始](getting-started/quick-start.md) | 选择 HugAgentOS Cloud、桌面客户端或一键安装,并启用 Personal Agent |
| [快速构建领域本体](getting-started/domain-ontology-quickstart.md) | 用最小 Domain Pack 跑通校验、发布、测试和工具绑定 |
## 部署
-先看 [部署指南(选择部署方式)](deployment/README.md),再按方式查阅:
+先看[部署指南(选择部署方式)](deployment/README.md),再按方式查阅:
| 文档 | 说明 |
|------|------|
| [部署指南 · 总览](deployment/README.md) | 各部署方式对比与选型、部署后验证 |
-| [无 Docker 一键安装](deployment/quick-install.md) | 个人单机零依赖:一条命令装好,SQLite + 进程内事件流/短时状态 + 子进程 MCP/沙箱 |
-| [Docker Compose 部署](deployment/docker-compose.md) | 团队/生产标准形态:全部服务拓扑、profiles、rebuild 流程、数据库迁移 |
-| [离线生产部署](deployment/offline-production.md) | 隔离环境:镜像 tarball 打包 / 生产侧加载 / 提示词快照迁移(商业版 EE) |
-| [Windows 部署](deployment/windows-deployment.md) | Docker Desktop / WSL2 部署差异、换行符、路径与沙箱限制 |
+| [Docker Compose 部署](deployment/docker-compose.md) | HugAgentOS Cloud 标准形态:服务拓扑、profiles、重建流程、数据库迁移 |
+| [桌面离线本机模式](deployment/desktop-local-mode.md) | HugAgentOS Local:私有运行时、仅本机 / 仅云端 / 本机 + 云端、升级与回滚 |
+| [本机项目工作目录](deployment/local-project-workspace.md) | 桌面端绑定本地项目后的执行目录与权限规则 |
+| [本机与云端技能、插件管理](deployment/local-capability-management.md) | 混合模式下技能与插件的来源和管理 |
+| [无 Docker 一键安装](deployment/quick-install.md) | 个人单机:SQLite + 进程内状态 + 子进程 MCP / 沙箱 |
+| [Windows 安装与部署](deployment/windows-deployment.md) | 桌面端单机服务或 Docker Desktop + WSL2 |
+| [macOS 签名与升级](deployment/macos-signing.md) | 桌面端签名、公证与升级 |
+| [我的空间文件挂载](deployment/space-files.md) | 网页与沙箱共用的文件目录 |
| [环境变量参考](deployment/environment-variables.md) | 全量环境变量逐组说明(默认值 / 作用 / CE·EE 相关性) |
## 架构
| 文档 | 说明 |
|------|------|
-| [总体架构](architecture/overview.md) | 分层架构图、一次对话的完整生命周期、容器拓扑、关键设计决策 |
-| [后端架构](architecture/backend.md) | `src/backend/` 全目录详解:api / orchestration / core 15 子模块 / 路由注册表 |
-| [前端架构](architecture/frontend.md) | 五入口分发、组件组、Zustand store、hooks、构建链 |
-| [数据模型](architecture/data-model.md) | 76 张表分组速览、alembic 双迁移链、CE/EE 表边界 |
+| [总体架构](architecture/overview.md) | 分层架构、本体控制平面、一次对话的完整生命周期、容器拓扑 |
+| [后端架构](architecture/backend.md) | `src/backend/` 目录详解与路由注册表 |
+| [前端架构](architecture/frontend.md) | 入口分发、组件组、Zustand store、模块页面与应用中心 |
+| [数据模型](architecture/data-model.md) | 表分组速览、迁移链、CE/EE 表边界 |
## 功能模块
+**Harness 与长时运行**
+
| 文档 | 说明 |
|------|------|
-| [对话与编排](modules/chat.md) | 端到端对话流、SSE 事件、引用系统、计划模式、子智能体、断线续播 |
-| [提示词系统](modules/prompts.md) | 装配优先级、版本池、提示词广场、跨环境迁移 |
-| [能力目录](modules/catalog.md) | catalog 单一真源、能力门控、用户自助能力 |
-| [模型接入](modules/model-providers.md) | 模型供应商与角色体系、动态切换、个人 API-Key、计费 |
-| [MCP 工具系统](modules/mcp-tools.md) | 内置及插件 Server、连接池、管理员/用户自定义 MCP |
-| [技能系统](modules/agent-skills.md) | SKILL.md 机制、技能包分层、技能市场、技能蒸馏 |
-| [沙箱执行](modules/sandbox.md) | 三种 provider、bash 工具、快照持久化、MySpace 直挂 |
-| [记忆系统](modules/memory.md) | L1/L2/L3 三层记忆、脱敏、审计、mem0 基础设施 |
-| [领域本体治理与校验](modules/ontology-harness.md) | CE 设置页治理、Domain Pack、工具门禁、交付前评审与人审演进 |
-| [知识库](modules/knowledge-base.md) | 自建知识库(混合检索)与 Dify 外接、公共库管理 |
-| [知识库 Wiki](modules/knowledge-base-wiki.md) | LLM 抽取实体与概念、生成互链条目页与概念图谱 |
-| [对象存储](modules/storage.md) | local / s3 / oss 三后端、产物仓、文件链路 |
-| [项目空间与我的空间](modules/projects-myspace.md) | 项目工作区、个人/团队文件夹、文件入上下文 |
-| [认证与权限](modules/auth.md) | AUTH_MODE 三模式、SSO、权限位、管理凭证 |
-| [管理台](modules/admin-console.md) | /admin 与 /config 双管理台、19 组管理路由 |
+| [对话与智能体编排](modules/chat.md) | Run 与连接解耦、断线续播、运行中转向、上下文按步恢复与压缩、计划模式、子智能体 |
+| [Personal Agent](modules/personal-agent.md) | 7×24 个人智能体:后台任务、永久云电脑、定时与心跳、IM 渠道、审批、文件记忆与 Dreaming |
+| [自主循环](modules/autonomous-loop.md) | 目标跨轮存活、maker≠checker、停滞护栏、断点续跑 |
+| [工作流后台作业](modules/workflow-jobs.md) | 沙箱受管进程、租约接管、派生主智能体 |
| [自动化与计划模式](modules/automation.md) | 定时任务调度、计划模式 |
-| [数据画布与产物](modules/canvas-artifacts.md) | Univer 画布、代码产物、Artifact 中心、会话分享 |
+
+**能力与扩展**
+
+| 文档 | 说明 |
+|------|------|
+| [MCP 工具系统](modules/mcp-tools.md) | 内置及插件 Server、连接池、自定义 MCP |
+| [技能系统](modules/agent-skills.md) | SKILL.md 机制、技能包分层、技能市场 |
+| [浏览器自动化插件](modules/browser-automation.md) | 浏览网页、操作表单与网页测试 |
+| [能力目录](modules/catalog.md) | catalog 单一真源、能力门控、用户自助能力 |
+| [沙箱执行](modules/sandbox.md) | provider、Bash 工具、快照持久化、我的空间直挂 |
+| [模型接入](modules/model-providers.md) | 模型供应商与角色体系、动态切换、个人 API-Key |
+| [提示词系统](modules/prompts.md) | 装配优先级、版本池、跨环境迁移 |
+
+**知识、记忆与治理**
+
+| 文档 | 说明 |
+|------|------|
+| [记忆系统](modules/memory.md) | L1/L2/L3 三层记忆、持久 Outbox、做法沉淀、脱敏与审计 |
+| [领域本体治理与校验](modules/ontology-harness.md) | Domain Pack、工具门禁、交付前评审、演进草案人工审核 |
+| [知识库](modules/knowledge-base.md) | 自建知识库(混合检索)与外接知识库 |
+| [知识库 Wiki](modules/knowledge-base-wiki.md) | 抽取实体与概念,生成互链条目页与概念图谱 |
+
+**工作区与产物**
+
+| 文档 | 说明 |
+|------|------|
+| [项目空间与我的空间](modules/projects-myspace.md) | 项目工作区、个人文件夹、云文档在线编辑、文件入上下文 |
+| [数据画布与产物](modules/canvas-artifacts.md) | Univer 画布、代码产物、产物中心、会话分享 |
+| [站点](modules/sites.md) | 对话建站与平台托管 |
+| [应用数据库与 MCP 托管](modules/application-hosting.md) | 应用独立数据库、表单数据与 MCP 发布 |
+| [对象存储](modules/storage.md) | 本地与 S3 兼容存储、产物仓、文件链路 |
+
+**平台管理**
+
+| 文档 | 说明 |
+|------|------|
+| [认证与权限](modules/auth.md) | AUTH_MODE、SSO、权限位、管理凭证 |
+| [管理台](modules/admin-console.md) | /admin 与 /config 管理台 |
## API 参考
@@ -67,21 +102,40 @@ HugAgentOS 是一个企业级 AI Agent 平台:FastAPI 后端 + React 前端 +
| 文档 | 说明 |
|------|------|
-| [社区版 vs 商业版](editions/overview.md) | open-core 模式、功能对比矩阵、三种运行形态、升级路径 |
-| [License 机制](editions/license.md) | 离线签名验签、状态机、能力位执法、签发工具(商业版 EE) |
+| [社区版 vs 商业版](editions/overview.md) | open-core 模式、功能对比矩阵、运行形态、升级路径 |
+| [License 机制](editions/license.md) | 离线签名验签、状态机、能力位执法(商业版 EE) |
| [CE 构建管线](editions/build-ce.md) | manifest、build_ce.py 流水、overlay、验收闸门 |
## 开发指南
| 文档 | 说明 |
|------|------|
-| [后端开发](development/backend.md) | Docker 内开发模式、测试、迁移、分层规范、新增路由/MCP/技能 |
-| [前端开发](development/frontend.md) | 构建热替、目录规范、API 调用约定、edition 门控 |
-
----
-
-内部设计与方案文档(CE/EE 拆分施工图、沙箱快照设计等)见 [docs 根目录](../README.md)。
+| [后端开发](development/backend.md) | 开发模式、测试、迁移、分层规范、新增路由 / MCP / 技能 |
+| [前端开发](development/frontend.md) | 构建、目录规范、API 调用约定、edition 门控 |
+| [客户对话界面定制](development/frontend-customization.md) | 页面配置、客户组件包与独立前端 |
-- [浏览器自动化插件](modules/browser-automation.md)
+**工作台应用(Workbench)**
-- [Personal Agent](modules/personal-agent.md)
+| 文档 | 说明 |
+|------|------|
+| [工作台协议](development/workbench-protocol.md) | JSON Schema、协议版本与接入方式 |
+| [Workbench SDK 与插件页面](development/workbench-sdk.md) | SDK 1.0.0、UI v3 插件页面 |
+| [开发预览](development/workbench-preview.md) | 独立工程与权限预览 |
+| [构建与离线制品](development/workbench-build.md) | 协议、SDK 与宿主前端的构建交付 |
+| [应用地址与插件导航](development/workbench-routing.md) | `/apps/` 地址与 applicationKey |
+| [应用资源](development/workbench-resources.md) | 包内 resource runtime |
+| [界面状态与有限保活](development/workbench-state.md) | 状态格式版本与保存恢复 |
+| [角标、通知与后台运行](development/workbench-activity.md) | badge、notification、background 能力 |
+| [平台适配](development/workbench-platform-adapters.md) | Web、Tauri 与 Electron 适配接口 |
+| [来源与离线安装](development/workbench-origins.md) | 本机与云端同名插件的入口 |
+| [插件升级与权限审批](development/workbench-plugin-upgrades.md) | 升级、重装与授权继承 |
+| [应用目录与布局策略](development/workbench-access-policies.md) | 目录接口、可用性与默认入口 |
+| [模块导航与应用目录](development/workbench-navigation-preferences.md) | 侧栏模块与应用中心 |
+| [目录通知与撤销](development/workbench-invalidation.md) | ETag 与目录失效通知 |
+| [普通外部应用](development/workbench-external-apps.md) | 外链应用的管理与使用 |
+| [外部应用认证](development/workbench-external-auth.md) | 外部应用认证配置(商业版 EE 管理台) |
+| [外部页面嵌入](development/workbench-external-embedding.md) | 嵌入工作区的 origin 与就绪确认 |
+| [应用诊断](development/workbench-diagnostics.md) | 诊断编号与重试 |
+| [错误码](development/workbench-errors.md) | 工作台错误码 |
+| [管理员指南](development/workbench-administration.md) | 工作台管理 |
+| [兼容与迁移](development/workbench-migration.md) | 版本兼容与迁移 |
diff --git a/document/zh-CN/api/overview.md b/document/zh-CN/api/overview.md
index 3906b512..8583aa0c 100644
--- a/document/zh-CN/api/overview.md
+++ b/document/zh-CN/api/overview.md
@@ -59,13 +59,13 @@ HugAgentOS 后端是一个 FastAPI 应用,所有业务接口挂在 `/v1/*` 前
| 方式 | 凭据携带方法 | 适用范围 | 实现 |
|---|---|---|---|
| **会话 Cookie** | `Cookie: jx_session=`(登录后自动种下) | 所有面向最终用户的 `/v1/*` 接口(chats、projects、kb、memories…) | `get_current_user`:Cookie → Redis 会话查找 |
-| **个人 API-Key** | `Authorization: Bearer sk-jx-...` | 与会话 Cookie 等价的用户身份,适合脚本/第三方集成调用 | `sk-jx-` 前缀识别;明文仅创建时返回一次,DB 只存 SHA256(`core/services/api_key_service.py`);在 `/v1/me/api-keys` 自助管理 |
+| **个人 API-Key** | `Authorization: Bearer sk-jx-...` | 与会话 Cookie 等价的用户身份,适合脚本/第三方集成调用 | `sk-jx-` 前缀识别;明文仅创建时返回一次,DB 只存 SHA256(`core/auth/api_keys.py`);在 `/v1/me/api-keys` 自助管理 |
| **Bearer token(mock/remote 模式)** | `Authorization: Bearer ` | `AUTH_MODE=mock`(开发:任意 token / 缺省 token 均放行为 mock 用户)或 `remote`(对接用户中心校验) | `get_current_user` 的兜底分支;`AUTH_MODE=session` 下不接受 |
| **ADMIN_TOKEN** | `Authorization: Bearer ` | `/admin` 管理台后端(`/v1/admin/skills`、`/v1/admin/kb` 等)及内容块写接口 | `require_admin`(环境变量 `ADMIN_TOKEN`) |
| **CONFIG_TOKEN** | `Authorization: Bearer ` | `/config` 系统配置台后端(`/v1/config/*`、`/v1/models`、`/v1/service-configs`、部分 `/v1/admin/*` 观测接口) | `require_config`(环境变量 `CONFIG_TOKEN`) |
| **ADMIN 或 CONFIG 任一** | 同上 | 提示词快照导入/导出(`/v1/content/prompts/export|import`) | `require_admin_or_config` |
| **super_admin 会话** | 会话 Cookie(用户 `extra_data.role == "super_admin"`),合法 ADMIN_TOKEN 可兜底 | 个别跨用户管理操作 | `require_super_admin` |
-| **BACKEND_INTERNAL_TOKEN** | `Authorization: Bearer ` | 站点、浏览器与安全工具的内部服务调用;未配置时拒绝请求 | `api/routes/v1/internal_site_auth.py`、`internal_security.py` |
+| **BACKEND_INTERNAL_TOKEN** | `Authorization: Bearer ` | 站点、浏览器与安全工具的内部服务调用;未配置时拒绝请求 | `features/sites/api/internal_site_auth.py`、`internal_security.py` |
鉴权优先级(`get_current_user`):**Cookie 会话 → API-Key Bearer(`sk-jx-` 前缀)→ mock/remote Bearer**。携带了形似 API-Key 的 Bearer 但校验失败时直接 401,绝不静默降级为匿名。
@@ -120,7 +120,7 @@ curl -X POST 'https:///api/v1/agents/responses' \
## SSE 流式协议
-流式聊天端点 `POST /v1/agents/responses`(`src/backend/api/routes/v1/agent_responses.py`,`stream: true`)校验通过后启动一个后台 run,再以 SSE 跟随该 run 实时下发事件;断线后可用 `GET /v1/chats/stream/{run_id}` 从任意偏移续播。事件由 `src/backend/orchestration/workflow.py` 产出、`src/backend/orchestration/chat_run_executor.py` 序列化上 wire。
+流式聊天端点 `POST /v1/agents/responses`(`src/backend/api/routes/v1/agent_responses.py`,`stream: true`)校验通过后启动一个后台 run,再以 SSE 跟随该 run 实时下发事件;断线后可用 `GET /v1/chats/stream/{run_id}` 从任意偏移续播。事件由 `src/backend/core/agent_runtime/runner.py` 产出、`src/backend/core/chat/runtime/` 序列化上 wire。
**Wire 格式**:`Content-Type: text/event-stream`,每个事件一行 `data: {JSON}`,事件类型放在 JSON 的 `type` 字段里(不使用 SSE 的 `event:` 行);流以 `data: [DONE]` 结束。静默超过 15 秒时发送 SSE 注释行 `: heartbeat` 维持反代连接。
@@ -197,7 +197,7 @@ data: [DONE]
答案必须与原问题数量、ID 和顺序完全一致;私有 option ID 必须来自 pending 响应,单选题不能提交多个选项,且每题必须作答或明确 `skipped=true`。后端会先把这些私有 ID 映射回原始选项 label,再向模型返回 DSH 兼容结果 `{"answers":[{"id":"scope","selected":["最小范围"]}]}`。第一个有效回答/取消会原子取得请求,之后的并发提交返回 `stale=true`,不会重复唤醒工具。成功 POST 已代表服务端原子取得请求,当前标签页可立即按 `request_id` 移除;`user_question_resolved` 和周期 pending 权威快照负责同步其他标签页与断线客户端。
-正文中的 `[ref:tool_name-N]` 引用标记由 `orchestration/citations.py` 解析为 `citations` 项,前端据此渲染角标(见 [对话模块](../modules/chat.md))。
+正文中的 `[ref:tool_name-N]` 引用标记由 `core/llm/citations/` 解析为 `citations` 项,前端据此渲染角标(见 [对话模块](../modules/chat.md))。
### 续播、Steer 与取消
diff --git a/document/zh-CN/architecture/backend.md b/document/zh-CN/architecture/backend.md
index ab242a68..beb8c6bb 100644
--- a/document/zh-CN/architecture/backend.md
+++ b/document/zh-CN/architecture/backend.md
@@ -128,7 +128,7 @@ src/backend/
`runner` 仅依赖不加载后端配置或数据库的进程和路径基础模块。OpenSandbox 专属组件留在自己的驱动目录;Cube 与 OpenSandbox 共用的进程、路径和账号环境能力由共享包提供。
-### core/agent_skills — 技能引擎
+### core/capabilities/skills — 技能引擎
| 文件 | 职责 |
|---|---|
@@ -144,12 +144,12 @@ src/backend/
| 子模块 | 职责与关键文件 |
|---|---|
| `core/chat` | workflow 上下文组装(`context.py`)、SSE 工具日志事件构造(`tool_log.py`) |
-| `core/content` | 附件解析(`file_parser.py`)、KB 文档分块/关键词/向量化(`kb_processing.py`)、上传校验(`file_validation.py`)、产物读取与摘要(`artifact_reader/refs/summary.py`)、内容块导入导出(`content_blocks.py`)、`svg_fit.py` |
-| `core/kb` / `edition_ee/kb` | 自建知识库解析与 Milvus 向量库保留在 `core/kb`;Dify 客户端与外部检索适配器只存在于 `edition_ee/kb`(商业版 EE) |
-| `core/artifacts` | 产物存储 `store.py`:本地 / OSS 双模式 |
-| `core/infra` | 统一响应(`responses.py`)、异常(`exceptions.py`)、结构化日志(`logging.py`)、限流(`rate_limit.py`)、Redis 单例(`redis.py`)、指标(`metrics.py`)、后台任务注册表(`runtime_state.py`)、脱敏(`data_masking.py`)、蒸馏预算闸门(`distillation_budget.py`,商业版 EE) |
+| `core/files/content` | 附件解析(`file_parser.py`)、KB 文档分块/关键词/向量化(`kb_processing.py`)、上传校验(`file_validation.py`)、产物读取与摘要(`artifact_reader/refs/summary.py`)、内容块导入导出(`content_blocks.py`)、`svg_fit.py` |
+| `features/knowledge` / `edition_ee/kb` | 自建知识库解析与 Milvus 向量库保留在 `features/knowledge`;Dify 客户端与外部检索适配器只存在于 `edition_ee/kb`(商业版 EE) |
+| `core/files/artifacts` | 产物存储 `store.py`:本地 / OSS 双模式 |
+| `core/infra` | 统一响应(`responses.py`)、异常(`exceptions.py`)、结构化日志(`logging.py`)、Redis 单例(`redis.py`)、指标(`metrics.py`)、后台任务注册表(`runtime_state.py`)、脱敏(`data_masking.py`)、蒸馏预算闸门(`distillation_budget.py`,商业版 EE) |
| `edition_ee/licensing` | 能力枚举、验签、时钟回拨防护、席位策略、中间件和 manager 全部只存在于商业版 EE;CE 树物理不含 License 包,由 `api/middleware/edition.py` 的 CE overlay 提供固定版本探针 |
-| `core/storage` | 存储协议 `protocol.py` + 工厂 `factory.py`;`local.py`(CE)、`s3.py` / `oss.py`(商业版 EE) |
+| `core/files/storage` | 存储协议 `protocol.py` + 工厂 `factory.py`;`local.py`(CE)、`s3.py` / `oss.py`(商业版 EE) |
## orchestration/ — 编排层
@@ -219,12 +219,12 @@ CE 路由位于 `api/routes/v1/`;已物理拆分的商业路由位于 `edition
| 应用入口与启动钩子 | `src/backend/api/app.py` |
| 路由注册表 | `src/backend/api/routes/v1/__init__.py` |
| 智能体工厂 | `src/backend/core/llm/factory/build.py` |
-| Run 执行器 / 工作流 | `src/backend/orchestration/chat_run_executor.py`、`workflow.py` |
-| 能力目录 | `src/backend/core/config/catalog.py` |
+| Run 执行器 / 工作流 | `src/backend/core/chat/runtime/`、`workflow.py` |
+| 能力目录 | `src/backend/core/capabilities/catalog/` |
| 沙箱协议 | `src/backend/core/sandbox/protocol.py` |
| 记忆流水线 | `src/backend/core/memory/pipeline.py` |
| EE License 实现 / CE 版本中间件 | `src/backend/edition_ee/licensing/`、`src/backend/api/middleware/edition.py` |
-| 技能引擎 | `src/backend/core/agent_skills/loader.py` |
+| 技能引擎 | `src/backend/core/capabilities/skills/loader.py` |
| MCP 注册表与端口 | `src/backend/extensions/mcp_servers/registry.py`、`_ports.py` |
空间 MCP 接口位于 `extensions/mcp_servers/space_files_mcp/`。注册表分别管理安装位置和暴露方式;混合模式通过桌面能力网关访问空间,修改操作接入统一审批。详见[空间文件挂载](../deployment/space-files.md)。
diff --git a/document/zh-CN/architecture/data-model.md b/document/zh-CN/architecture/data-model.md
index 8a99e9f7..126931cd 100644
--- a/document/zh-CN/architecture/data-model.md
+++ b/document/zh-CN/architecture/data-model.md
@@ -179,11 +179,11 @@ user_feedback # 问题反馈审核 /
|---|---|
| 共享 ORM / EE ORM | `src/backend/core/db/models/`、`src/backend/edition_ee/db/models/` |
| 本体仓储 | `src/backend/core/db/repository/ontology.py` |
-| 问题反馈模型 / 仓储 | `src/backend/edition_ee/db/models/feedback.py`、`db/repository/feedback.py` |
+| 问题反馈模型 / 仓储 | `src/backend/edition_ee/db/models/feedback.py`、`edition_ee/db/repository/feedback.py` |
| 引擎与启动建表 | `src/backend/core/db/engine.py` |
| 仓储层 | `src/backend/core/db/repository/` |
-| CE/EE 建表边界 | `src/backend/core/db/edition_tables.py` |
+| CE/EE 建表边界 | `src/backend/edition_ee/db/edition_tables.py` |
| EE 迁移主链 | `src/backend/alembic/versions/` |
| CE 迁移基线 | `ce/overlay/src/backend/alembic/versions/ce_0001_initial.py` |
-| 提示词版本池服务 | `src/backend/core/services/prompt_version_service.py` |
+| 提示词版本池服务 | `src/backend/core/llm/prompt_versions.py` |
| 版本边界权威方案 | [版本与授权](../editions/overview.md) |
diff --git a/document/zh-CN/architecture/overview.md b/document/zh-CN/architecture/overview.md
index 9219e0e8..06677e4c 100644
--- a/document/zh-CN/architecture/overview.md
+++ b/document/zh-CN/architecture/overview.md
@@ -1,8 +1,8 @@
# 系统整体架构
-> 最后更新:2026-07-19
+> 最后更新:2026-10-23
-HugAgentOS 是一个面向企业的 AgentOS,同时提供轻量本地运行形态和全容器化生产形态。前端为 React 单页应用,后端为 FastAPI 异步服务,智能体底座为 AgentScope 2.0 的 ReActAgent,工具生态通过 MCP(Model Context Protocol)以独立进程提供,代码执行落在可插拔的沙箱驱动上。平台以「开源社区版(CE)+ 商业版(EE)」双形态发行,两版共用一套代码,通过路由注册表、建表边界与 license 能力位三道接缝裁剪(详见 [版本与授权](../editions/overview.md))。
+HugAgentOS 是一个开源、可自托管的 Agent Harness,提供 HugAgentOS Cloud(全容器化服务端)与 HugAgentOS Local(桌面本机)两种运行形态。前端为 React 单页应用,后端为 FastAPI 异步服务,智能体底座为 AgentScope 2.0 的 ReActAgent,工具生态通过 MCP(Model Context Protocol)以独立进程提供,代码执行落在可插拔的沙箱驱动上。平台以「开源社区版(CE)+ 商业版(EE)」双形态发行,两版共用一套代码,通过路由注册表、建表边界与 license 能力位三道接缝裁剪(详见 [版本与授权](../editions/overview.md))。
本文从五个视角描述系统:分层架构、本体驱动的企业可信控制平面、一次对话请求的完整生命周期、容器拓扑、关键设计决策。更深入的模块拆解见 [后端架构](./backend.md)、[前端架构](./frontend.md) 与 [数据模型](./data-model.md)。
@@ -23,48 +23,38 @@ HugAgentOS 是一个面向企业的 AgentOS,同时提供轻量本地运行形
```
┌────────────────────────────────────────────────────────────────────┐
-│ 浏览器(React SPA) │
-│ App.tsx 主应用 · AdminApp 内容台(EE) · ConfigApp 系统台(EE) │
-│ api.ts 类型化客户端 + useStreaming SSE 解析 │
+│ 入口:Web(React SPA)· 桌面客户端(Tauri)· IM 渠道 · API · 定时 · 心跳 │
└──────────────────────────────┬─────────────────────────────────────┘
│ HTTP / SSE(/api 前缀)
┌──────────────────────────────▼─────────────────────────────────────┐
-│ Nginx(frontend 容器,:80) │
-│ 静态资源托管 · /api → backend 反向代理 │
+│ FastAPI(src/backend/api/) │
+│ 中间件:CORS · 结构化日志 · 统一错误处理 · 版本路由注册表 │
└──────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────────┐
-│ FastAPI(backend 容器,src/backend/api/) │
-│ 中间件:CORS · 结构化日志 · 统一错误处理 │
-│ 路由注册表:CE_ROUTERS + EE_ROUTERS(license 能力位守卫) │
+│ Run 层 │
+│ core/runs:持久 Run、数据库租约、启动对账、工具副作用意图与回执 │
+│ core/chat/runtime:Run 准入、聊天 worker、续播、steer、压缩 │
└──────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────────┐
-│ 编排层(src/backend/orchestration/) │
-│ chat_run_executor:Run 与 HTTP 连接解耦、断线续播 │
-│ workflow:流式编排 · strategy:路由策略 · citations:引用提取 │
-│ memory_integration:记忆读写 · subagents/plan_mode:计划模式 │
-│ schedulers:自动化定时任务 · 技能蒸馏 cron(EE) │
-└──────────────────────────────┬─────────────────────────────────────┘
- │
-┌──────────────────────────────▼─────────────────────────────────────┐
-│ 智能体层(src/backend/core/llm/) │
-│ factory/build → AgentScope 2.0 ReActAgent │
-│ ToolCollector → Toolkit(自研工具 + 技能 + MCP 工具) │
-│ middlewares:动态模型/文件上下文/系统提醒 · offloader:超长结果落盘 │
-│ prompts/prompt_runtime:系统提示词装配(DB 优先,文件兜底) │
+│ 智能体执行 │
+│ core/agent_runtime:runner.astream_chat_workflow、路由策略、 │
+│ 记忆 / 本体 / 进化集成 │
+│ core/llm:factory/build → AgentScope ReActAgent、工具、上下文、引用 │
+│ orchestration/:plans 计划模式 · loops 自主循环 · automations 自动化 │
+│ personal_agent/:身份、任务队列、调度、渠道、设备连接、工作区 │
└───────┬──────────────────────┬──────────────────────┬──────────────┘
│ streamable-http │ 沙箱驱动协议 │ SQL / 向量
┌───────▼───────────┐ ┌────────▼──────────┐ ┌─────────▼─────────────┐
-│ MCP 工具池 │ │ 沙箱 │ │ 数据层 │
-│ mcp 工具进程 │ │ script-runner(CE) │ │ PostgreSQL · Redis │
-│ 端口见 _ports.py │ │ OpenSandbox(CE/EE) │ │ Milvus · Neo4j(可选) │
-│ 搜索/抓取/插件工具 │ │ CubeSandbox(EE) │ │ 本地 / S3 / OSS 存储 │
-│ 数仓/产业链(EE) │ │ │ │ │
+│ MCP 工具池 │ │ 沙箱与作业 │ │ 数据层 │
+│ mcp 工具进程 │ │ OpenSandbox │ │ PostgreSQL / SQLite │
+│ 端口见 _ports.py │ │ script-runner │ │ Redis · Milvus · Neo4j│
+│ 插件工具 │ │ core/jobs 后台作业 │ │ S3 兼容 / 本地存储 │
└───────────────────┘ └───────────────────┘ └───────────────────────┘
```
-依赖方向自上而下单向:`api → orchestration → core`,`core` 内部 `services → db`。MCP 服务器是独立进程,与后端只通过 HTTP 通信,崩溃互不影响。
+依赖方向自上而下单向:`api → features / orchestration / personal_agent → core`;`core` 不依赖上层,`integrations/` 承载钉钉、飞书、邮箱等外部系统接入。MCP 服务器是独立进程,与后端只通过 HTTP 通信,崩溃互不影响。
## 领域本体驱动的企业可信控制平面
@@ -139,21 +129,22 @@ RAG 则提供支撑决策所需的文档与证据。
### 2. 启动 Run(与 HTTP 连接解耦)
-`orchestration/chat_run_executor.py::start_run` 把 AI 工作流注册为后台 Run(落 `chat_runs` 表),HTTP 响应只是「跟随」这个 Run:
+`core/chat/runtime/admission.py::start_run` 把本轮注册为后台 Run(落 `chat_runs` 表),由持有数据库租约的聊天 worker 执行;HTTP 响应只是「跟随」这个 Run:
-- 浏览器断线、刷新页面后可调 `GET /v1/chats/stream/{run_id}` 从指定偏移续播;
-- 后端重启时 lifespan 钩子 `_startup_recover_chat_runs` 恢复/收割未完成的 Run。
+- 浏览器断线、刷新页面后可调 `GET /v1/chats/stream/{run_id}?from=N` 从指定偏移续播;
+- 运行中可通过 `POST /v1/chat-runs/{run_id}/steer` 追加指令,在下一个安全 ReAct 边界生效;
+- 后端启动时 `core/chat/runtime/recovery.py::recover_orphan_runs` 依据最后提交的安全点对账租约过期的 Run:可恢复的继续,不可恢复的明确收尾。
### 3. 编排与智能体执行
-`orchestration/workflow.py::astream_chat_workflow` 驱动单轮执行:
+`core/agent_runtime/runner.py::astream_chat_workflow` 驱动单轮执行:
-1. **记忆检索**——`orchestration/memory_integration.py` 读取 L1 画像 / L2 向量记忆(开启时),注入系统消息;
-2. **路由决策**——`orchestration/strategy.py`,`ROUTER_STRATEGY=main_only`(默认)恒路由到主智能体;
+1. **记忆检索**——`core/agent_runtime/integrations/memory.py` 读取 L1 画像 / L2 向量记忆(开启时),注入系统消息;
+2. **路由决策**——`core/agent_runtime/strategy.py`,`ROUTER_STRATEGY=main_only`(默认)恒路由到主智能体;
3. **构建智能体**——`core/llm/factory/build.py::create_agent_executor`:装配提示词(`prompts/prompt_runtime.py`,DB 版本池优先)、按 catalog 与用户能力位筛选 MCP 服务器、注册自研工具(Read/Write/Edit/Glob/Grep/bash/技能加载等,见 `core/llm/tools/`)、最终一次性构造 AgentScope Toolkit;
-4. **流式执行**——`orchestration/streaming.py::StreamingAgent` 包装 ReActAgent,把推理增量、工具调用、工具结果逐条产出;
-5. **引用提取**——`orchestration/citations.py` 从工具结果中解析 `[ref:tool_name-N]` 标记生成引用项;
-6. **收尾**——落库助手消息与工具调用日志,`core/memory/pipeline.py` 在后台异步抽取记忆(不阻塞 SSE 主链路),独立接口生成追问问题(`orchestration/followups.py`)。
+4. **流式执行**——`core/llm/runtime/streaming.py::StreamingAgent` 包装 ReActAgent,把推理增量、工具调用、工具结果逐条产出;
+5. **引用提取**——`core/llm/citations/` 从工具结果中解析 `[ref:tool_name-N]` 标记生成引用项;
+6. **收尾**——落库助手消息与工具调用日志,`core/memory/pipeline.py` 在后台异步抽取记忆(不阻塞 SSE 主链路),独立接口生成追问问题(`core/chat/followups.py`)。
### 4. SSE 事件流
@@ -166,9 +157,9 @@ RAG 则提供支撑决策所需的文档与证据。
| `tool_call_start` | 工具调用开始(稳定 ID + 名称) | `core/chat/tool_log.py` |
| `tool_call_delta` | 合批后的工具参数 JSON 增量 | `core/chat/tool_log.py` |
| `tool_call` | 工具参数完整、即将执行 | `core/chat/tool_log.py` |
-| `tool_result` | 工具执行结果(含引用、产物卡片载荷) | `core/chat/tool_log.py` + `orchestration/tool_payloads.py` |
+| `tool_result` | 工具执行结果(含引用、产物卡片载荷) | `core/chat/tool_log.py` + `core/chat/tool_payloads.py` |
| `tool_progress` | 长工具的进度上报 | `chats.py` |
-| `meta` | 末尾元信息:路由、引用源、产物列表等 | `orchestration/workflow.py` |
+| `meta` | 末尾元信息:路由、引用源、产物列表等 | `core/agent_runtime/runner.py` |
| `error` | 错误事件(随后立即 `[DONE]`) | `chats.py` |
前端 `src/frontend/src/hooks/useStreaming.ts` 按 `type` 分发,并按 `tool_id` 把 start、delta、最终调用与结果原位合并为一张工具卡片。
@@ -181,13 +172,14 @@ RAG 则提供支撑决策所需的文档与证据。
|---|---|---|---|
| `frontend` | hugagent-frontend | `3002:80` | Nginx:静态资源 + `/api` 反代 |
| `backend` | hugagent-backend | `3001` | FastAPI 主服务 |
-| `mcp` | hugagent-mcp | 内部独立端口 | 9 个 MCP 服务器常驻进程(streamable-http;CE 7 个通用工具,EE 另含 2 个行业工具) |
+| `mcp` | hugagent-mcp | 内部独立端口 | 内置 MCP 服务器常驻进程(streamable-http;数仓查询与行业工具为 EE) |
| `postgres` | hugagent-postgres | `5432` | 主数据库 |
| `redis` | hugagent-redis | `6379` | 会话 / 缓存 |
-| `script-runner`(profile `script_runner`) | hugagent-script-runner | 内部 | 轻量脚本执行 sidecar(无 DB/密钥访问权) |
-| `opensandbox` + `opensandbox-config-init`(profile `opensandbox`) | hugagent-opensandbox | 内部 | 持久沙箱(商业版 EE) |
+| `script-runner`(profile `script_runner`) | hugagent-script-runner | 内部 | 可选的轻量脚本执行 sidecar(无 DB/密钥访问权) |
+| `opensandbox` + `opensandbox-config-init`(profile `opensandbox`) | hugagent-opensandbox | 内部 | 默认沙箱:持久会话、快照恢复(与 script-runner 互斥) |
| `etcd` / `minio` / `milvus`(profile `mem0`) | hugagent-etcd 等 | 内部 | L2 向量记忆基础设施 |
| `neo4j`(profile `mem0`) | hugagent-neo4j | `7474/7687` | L3 图谱记忆(可选) |
+| `personal-agent-opensandbox`(profile `personal-agent`,`docker/personal-agent/compose.personal-agent.yml`) | — | 内部 | Personal Agent 永久云电脑的专用控制器 |
所有服务挂在同一 `hugagent-network` 网络;`postgres_data` 等具名卷跨重建持久化。部署细节见 [Docker Compose 部署](../deployment/docker-compose.md)。
@@ -195,7 +187,7 @@ RAG 则提供支撑决策所需的文档与证据。
### Catalog 单一真源
-`src/backend/core/config/catalog.json` 是能力开关的唯一权威,四类条目:`skills` / `agents` / `mcp` / `kb`。`core/config/catalog.py` 暴露 `is_enabled(kind, id)` / `get_enabled_ids(kind)`,从 MCP 工具装载、技能注册到路由决策全部经此门控;管理台的启停操作写 `catalog_overrides` 表叠加在文件之上(`core/config/catalog_loader.py`)。新能力只要进 catalog 即可被前端能力中心与智能体工厂同时感知,无需改编排代码。详见 [能力目录](../modules/catalog.md)。
+`src/backend/core/config/catalog.json` 是能力开关的唯一权威,四类条目:`skills` / `agents` / `mcp` / `kb`。`core/capabilities/catalog/` 暴露 `is_enabled(kind, id)` / `get_enabled_ids(kind)`,从 MCP 工具装载、技能注册到路由决策全部经此门控;管理台的启停操作写 `catalog_overrides` 表叠加在文件之上(`core/capabilities/catalog/loader.py`)。新能力只要进 catalog 即可被前端能力中心与智能体工厂同时感知,无需改编排代码。详见 [能力目录](../modules/catalog.md)。
### 统一响应信封
@@ -203,19 +195,19 @@ RAG 则提供支撑决策所需的文档与证据。
### 提示词 DB 化
-系统提示词不再硬编码:版本池存于 `content_blocks` 表(`id=prompt_versions`),由 `core/services/prompt_version_service.py` 管理 CRUD、激活与首启 seed;`prompts/prompt_runtime.py` 装配时按「DB 激活版本 → `prompts/prompt_text/default/system/` 文件兜底 → 最小硬编码兜底」三级降级,保证永不为空。运营可在管理台灰度切换提示词版本而无需发版。详见 [提示词系统](../modules/prompts.md)。
+系统提示词不再硬编码:版本池存于 `content_blocks` 表(`id=prompt_versions`),由 `core/llm/prompt_versions.py` 管理 CRUD、激活与首启 seed;`prompts/prompt_runtime.py` 装配时按「DB 激活版本 → `prompts/prompt_text/default/system/` 文件兜底 → 最小硬编码兜底」三级降级,保证永不为空。运营可在管理台灰度切换提示词版本而无需发版。详见 [提示词系统](../modules/prompts.md)。
### MCP 工具独立进程
-每个工具是 `src/backend/extensions/mcp_servers//` 下的独立 MCP 服务器,统一运行在专用 `mcp` 容器内,以 streamable-http 常驻监听(端口表 `extensions/mcp_servers/_ports.py` 为单一真源),后端经 `core/llm/mcp_manager.py` 的 HTTP 客户端连接池复用连接。好处:工具崩溃不影响主进程、依赖隔离(文档编辑与导出已迁入沙箱技能)、可独立扩缩。CE 保留 7 个通用 Server;`query_database` 与 `ai_chain_information_mcp` 两个行业工具依赖内网数据源,归商业版 EE。详见 [MCP 工具](../modules/mcp-tools.md)。
+每个工具是 `src/backend/extensions/mcp_servers//` 下的独立 MCP 服务器,统一运行在专用 `mcp` 容器内,以 streamable-http 常驻监听(端口表 `extensions/mcp_servers/_ports.py` 为单一真源),后端经 `core/llm/mcp/manager.py` 的 HTTP 客户端连接池复用连接。好处:工具崩溃不影响主进程、依赖隔离(文档编辑与导出已迁入沙箱技能)、可独立扩缩。`query_database` 与 `ai_chain_information_mcp` 两个行业工具依赖内网数据源,归商业版 EE。详见 [MCP 工具](../modules/mcp-tools.md)。
-### Run 与连接解耦
+### Run 与连接、进程解耦
-流式对话不绑定 HTTP 连接生命周期:`chat_run_executor` 把执行状态写入 `chat_runs` 表并支持按偏移续播,彻底解决「刷新页面丢回复」「弱网断流」问题,也让自动化调度器能复用同一执行通道。
+流式对话不绑定 HTTP 连接,也不绑定某个进程:Run 状态与按步上下文落库,worker 通过数据库租约领取执行,过期执行者不能再提交结果或调用工具;进程中断后由其他 worker 接管。工具调用先记录意图、有回执才算完成(`core/runs/tool_effects/`),恢复时只重放能证明尚未生效的调用,结果未知的外部操作交给用户确认。自动化、计划模式、自主循环与 Personal Agent 复用同一套 Run 机制。
### CE/EE 同源双形态
-商业版主仓即全量代码;社区版由 `scripts/build_ce.py` 按 `ce/manifest.yaml` 派生(排除 `edition_ee` 实现 + 文本变换 + CE overlay)。运行时边界包括路由注册表 `api/routes/v1/__init__.py`、建表边界 `core/db/edition_tables.py`(CE 不注册 20 张 EE 专属表)、EE 的 `edition_ee/licensing/` 与 CE 固定版本中间件,以及站点可见性等版本策略缝隙。详见 [版本与授权](../editions/overview.md)。
+商业版主仓即全量代码;社区版由 `scripts/build_ce.py` 按 `ce/manifest.yaml` 派生(排除 `edition_ee` 实现 + 文本变换 + CE overlay)。运行时边界包括路由注册表 `api/routes/v1/__init__.py`、建表边界 `edition_ee/db/edition_tables.py`(CE 不注册 20 张 EE 专属表)、EE 的 `edition_ee/licensing/` 与 CE 固定版本中间件,以及站点可见性等版本策略缝隙。详见 [版本与授权](../editions/overview.md)。
## 相关源码
@@ -223,12 +215,13 @@ RAG 则提供支撑决策所需的文档与证据。
|---|---|
| FastAPI 应用与启动钩子 | `src/backend/api/app.py` |
| 流式对话入口 | `src/backend/api/routes/v1/chats/__init__.py` |
-| Run 执行器 | `src/backend/orchestration/chat_run_executor.py` |
-| 流式编排 | `src/backend/orchestration/workflow.py` |
+| Run 执行器 | `src/backend/core/chat/runtime/` |
+| 流式编排 | `src/backend/core/agent_runtime/runner.py` |
| 智能体工厂 | `src/backend/core/llm/factory/build.py` |
| MCP 配置与端口 | `src/backend/core/config/mcp_config.py`、`src/backend/extensions/mcp_servers/_ports.py` |
-| 能力目录 | `src/backend/core/config/catalog.json`、`catalog.py` |
+| 能力目录 | `src/backend/core/config/catalog.json`、`src/backend/core/capabilities/catalog/` |
+| Run、租约与恢复 | `src/backend/core/runs/`、`src/backend/core/chat/runtime/recovery.py` |
| 响应信封 | `src/backend/core/infra/responses.py` |
| 提示词装配 | `src/backend/prompts/prompt_runtime.py` |
-| CE/EE 接缝 | `src/backend/api/routes/v1/__init__.py`、`src/backend/core/db/edition_tables.py`、`src/backend/api/middleware/edition.py`、`src/backend/edition_ee/licensing/` |
+| CE/EE 接缝 | `src/backend/api/routes/v1/__init__.py`、`src/backend/edition_ee/db/edition_tables.py`、`src/backend/api/middleware/edition.py`、`src/backend/edition_ee/licensing/` |
| 容器编排 | `docker-compose.yml` |
diff --git a/document/zh-CN/deployment/README.md b/document/zh-CN/deployment/README.md
index 5a64dba2..cb1ce781 100644
--- a/document/zh-CN/deployment/README.md
+++ b/document/zh-CN/deployment/README.md
@@ -9,10 +9,8 @@ HugAgentOS 支持多种部署方式,从「个人单机零依赖尝鲜」到「
| 部署方式 | 适用场景 | Docker | 数据库 | 多用户 | 文档 |
|---|---|---|---|---|---|
| **桌面端离线本机模式** | Windows / macOS / Linux 个人用户;安装后直接使用 | 不需要 | SQLite | 否(单用户) | [desktop-local-mode.md](desktop-local-mode.md) |
-| **UOS 1070 ARM64 桌面端** | 统信 UOS 1070 aarch64;Electron 43 `.deb` | 不需要 | SQLite / 远程 | 本机单用户 / 云端多用户 | [uos1070-desktop.md](uos1070-desktop.md) |
| **无 Docker 一键安装** | 个人单机尝鲜、二次开发体验;一条命令装好即用 | 不需要 | SQLite | 否(单用户) | [quick-install.md](quick-install.md) |
| **Docker Compose** | 团队 / 生产的标准形态,多用户、全功能 | 需要 | PostgreSQL | 是 | [docker-compose.md](docker-compose.md) |
-| **离线生产部署(商业版 EE)** | 政务 / 内网等隔离环境,镜像 tarball 离线交付 | 需要 | PostgreSQL | 是 | [offline-production.md](offline-production.md) |
跨平台与参考:
@@ -20,7 +18,6 @@ HugAgentOS 支持多种部署方式,从「个人单机零依赖尝鲜」到「
|---|---|
| [Windows 部署](windows-deployment.md) | 桌面端本机服务一键安装,或以 Docker Desktop + WSL2 跑 Compose 版 |
| [桌面端离线本机模式](desktop-local-mode.md) | 三平台随包私有 Python、原子升级回滚与完整/精简包 |
-| [UOS 1070 ARM64 桌面端](uos1070-desktop.md) | Electron 43、glibc 2.28、ARM64 `.deb` 构建与验收 |
| [环境变量参考](environment-variables.md) | 全量环境变量逐组说明(默认值 / 作用 / CE·EE 相关性) |
## 一句话对比
@@ -30,7 +27,6 @@ HugAgentOS 支持多种部署方式,从「个人单机零依赖尝鲜」到「
联网构建。定位是单进程单用户。
- **无 Docker 一键安装**:Linux/macOS 上最快的命令行路径。运行 `curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh | bash`,安装器会创建管理员、配置模型、启动服务并打开浏览器。数据保存在 `~/.hugagent/`。**单进程单用户**,适合个人试用与开发,不适合多人或生产环境。
- **Docker Compose**:推荐的标准部署。全部服务由一个 `docker-compose.yml` 编排(PostgreSQL + Redis + 后端 + MCP + 前端 + 沙箱),支持多用户、持久沙箱、分层记忆等全部能力。
-- **离线生产部署(EE)**:面向无法在线拉镜像的隔离环境,在联网侧打镜像 tarball,拷到生产侧 `docker load` + `compose up`。属商业版交付范畴。
> 能力对比、社区版 / 商业版差异见 [版本对比](../editions/overview.md)。
diff --git a/document/zh-CN/deployment/artifact-storage-key-repair.md b/document/zh-CN/deployment/artifact-storage-key-repair.md
deleted file mode 100644
index 0a63ffd0..00000000
--- a/document/zh-CN/deployment/artifact-storage-key-repair.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# 历史产物存储地址修复
-
-适用于旧版本把 `artifacts/.docx` 等对象登记为 `artifacts/` 的问题。
-当前生成流程要求真实存储地址,个人空间文件列表只等待本地改动登记,不再等待所有云端文件下载。
-导入窗口把请求失败显示为错误并提供重试,失败不会再显示为“暂无文件”。
-
-## 安全边界
-
-修复只匹配 `storage_key == artifacts/`。必须确认旧对象不存在、
-对应文件名后缀的对象存在、下载字节数与原登记大小一致。服务超时、鉴权失败、
-大小不符或找不到替代对象时,保留原记录。真实存在的无后缀对象保持不变。
-
-写入采用带原地址、文件名、大小、更新时间条件的原子更新;不修改内容版本。
-原地址、新地址、SHA-256 和原内容版本与新地址一起存入
-`metadata.storage_key_repair`,支持审计及有条件回滚。
-个人空间投影下载失败时会用同样的校验自动修复;未保存的本地内容仍按原有冲突保护处理。
-
-## 执行
-
-遵守仓库发布授权:预发验证后,生产代码与数据变更必须另行取得用户确认。
-在已授权环境的项目根目录执行,使用该环境已有配置与 Python 运行时。
-不要复制其他环境的 `.env` 或凭据。
-
-```bash
-# 默认只验证、输出候选地址,不改数据库。
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id <用户ID>
-
-# 确认候选结果后执行;重复运行不会重复修改已修复项。
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id <用户ID> --apply
-
-# 仅回滚内容版本、当前地址都未变化的已审计修复。
-# 回滚会恢复旧地址;若后台投影已升级,仍可能再次进行自动修复。
-PYTHONPATH=src/backend python scripts/repair_artifact_storage_keys.py --user-id <用户ID> --rollback
-```
-
-工具一次只处理明确指定的账号,按文件 ID 分批读取。错误退出非零,避免把部分失败当作全部成功。
-修复既有记录包含回收站中的历史项;不会恢复已删除文件或变更所属目录。
-
-## 验收
-
-1. 地址修复候选、对象大小和审计记录正确;再次运行没有重复改写。
-2. 个人空间根目录和原文件目录的列表 API 返回成功;有效文件能下载。
-3. Windows 桌面本机模式分别用 `@` 和 `+ → 从我的空间导入` 选择文件,实际发送后可读取正文。
-4. 注入单个不存在的对象时,其他文件列表可用;文件获取仍报告真实失败。
-5. 请求失败显示错误和重试;切换筛选或目录后旧响应不能覆盖当前结果。
-
-前端源码变化必须随桌面客户端发行才对已安装客户端生效。完成源码构建不代表已经发布,
-也不代表现有生产数据已修改;发布记录应注明预发、生产和客户端各自的验证结果。
diff --git a/document/zh-CN/deployment/desktop-local-mode.md b/document/zh-CN/deployment/desktop-local-mode.md
index 9add6669..2459a1ff 100644
--- a/document/zh-CN/deployment/desktop-local-mode.md
+++ b/document/zh-CN/deployment/desktop-local-mode.md
@@ -1,7 +1,6 @@
# 桌面端离线本机模式
-> 适用于 Windows x86_64、macOS Apple Silicon / Intel、Linux x86_64;UOS 1070
-> aarch64 使用独立的 [Electron 43 桌面发行线](uos1070-desktop.md)。
+> 适用于 Windows x86_64、macOS Apple Silicon / Intel、Linux x86_64。
桌面端默认发行“完整离线包”。安装包同时携带当前平台的 CE 服务源码和私有 CPython 3.11
运行时,最终用户不需要安装 Python、uv、pip、编译器或 Docker,也不需要在首次启动时访问
diff --git a/document/zh-CN/deployment/docker-compose.md b/document/zh-CN/deployment/docker-compose.md
index d16978e4..8ede9286 100644
--- a/document/zh-CN/deployment/docker-compose.md
+++ b/document/zh-CN/deployment/docker-compose.md
@@ -14,7 +14,7 @@ CE 的 Docker 部署默认使用 OpenSandbox;无 Docker 安装及桌面本机
```bash
python3 scripts/configure-ce-docker.py
-docker build -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+docker build -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
docker compose build backend
docker compose up -d --build
```
@@ -65,8 +65,8 @@ CE 在空数据库首次启动时会全局安装并启用 `automation`、
| 服务 | profile | 容器名 | 镜像 / 构建 | 作用 |
|---|---|---|---|---|
-| `script-runner` | `script_runner` | hugagent-script-runner | `docker/Dockerfile.script-runner` | 轻量沙箱:1 GB 内存 / 1 CPU / read-only rootfs + tmpfs,技能目录只读挂载到 `/workspace/skills` |
-| `opensandbox-config-init` | `opensandbox` | hugagent-opensandbox-config-init | `python:3.12-alpine` | 一次性 init:把 `docker/opensandbox-config.toml.tpl` 中的 `@@HOST_REPO_PATH@@` / `@@HOST_STORAGE_PATH@@` 渲染进 named volume `opensandbox_config` |
+| `script-runner` | `script_runner` | hugagent-script-runner | `docker/sandbox/Dockerfile.script-runner` | 轻量沙箱:1 GB 内存 / 1 CPU / read-only rootfs + tmpfs,技能目录只读挂载到 `/workspace/skills` |
+| `opensandbox-config-init` | `opensandbox` | hugagent-opensandbox-config-init | `python:3.12-alpine` | 一次性 init:把 `docker/opensandbox-server/opensandbox-config.toml.tpl` 中的 `@@HOST_REPO_PATH@@` / `@@HOST_STORAGE_PATH@@` 渲染进 named volume `opensandbox_config` |
| `opensandbox` | `opensandbox` | hugagent-opensandbox | `hugagent-opensandbox-server:v0.1.13-3` | CE / EE 共享控制器:经 Docker socket 管理沙盒;execd 执行命令并保留会话文件。CE 不映射宿主端口;全量仓库可配置调试端口 |
### mem0 记忆基础设施(profile `mem0`,可选)
@@ -214,19 +214,19 @@ make migrate-new msg="describe change"
| 功能 | 文件 |
|---|---|
-| 服务编排 | `docker-compose.yml`(CubeSandbox 叠加:`docker-compose.cube.yml`) |
+| 服务编排 | `docker-compose.yml`(CubeSandbox 叠加:`docker/sandbox/compose.cube.yml`) |
| 后端镜像 | `docker/Dockerfile`(multi-stage,target `production`) |
| MCP 镜像 | `docker/Dockerfile.mcp` |
-| 沙箱镜像 | `docker/Dockerfile.script-runner`、`docker/Dockerfile.opensandbox` |
+| 沙箱镜像 | `docker/sandbox/Dockerfile.script-runner`、`docker/sandbox/Dockerfile.opensandbox` |
| 前端镜像 + nginx 反代 | `src/frontend/Dockerfile`、`src/frontend/nginx.conf`、`src/frontend/default.conf.template` |
| 后端入口(自动迁移) | `src/backend/scripts/backend_entrypoint.sh` |
-| OpenSandbox 配置模板 | `docker/opensandbox-config.toml.tpl` |
+| OpenSandbox 配置模板 | `docker/opensandbox-server/opensandbox-config.toml.tpl` |
| 迁移脚本 | `alembic.ini`、`src/backend/alembic/` |
| MCP 端口单一真源 | `src/backend/extensions/mcp_servers/_ports.py` |
### OpenSandbox 命令延迟与控制器镜像
-控制器使用 `docker/Dockerfile.opensandbox-server` 构建
+控制器使用 `docker/opensandbox-server/Dockerfile.opensandbox-server` 构建
`hugagent-opensandbox-server:v0.1.13-3`。它基于固定的上游版本,对已知沙盒使用
按名称 inspect 并校验归属标签,且在线程池中执行生命周期与代理的同步 Docker 查询,
避免阻塞其它请求。构建时先校验上游源文件摘要并运行回归测试;升级上游版本时须重新审查补丁。
@@ -239,7 +239,7 @@ make migrate-new msg="describe change"
## 站点数据与浏览器部署检查
-普通 Compose 启动不启用应用数据托管,也不重建 OpenSandbox 按需运行镜像。需要这两项能力时,部署前分别完成[应用数据升级](../modules/application-hosting.md#外部检验修复与升级)和[浏览器运行镜像检查](../modules/browser-automation.md)。配置不同的 APPLICATION_DB_PASSWORD 与 APPLICATION_OWNER_DB_PASSWORD、共享 Redis 和适合网络环境的 BROWSER_DNS_RESOLVER_URL;使用 application_owner 作为应用数据运行账号。已有数据库卷也必须重新执行 application-database-init,然后更新后端、MCP、前端及沙箱运行镜像,并创建新浏览器会话进行验证。
+普通 Compose 启动不启用应用数据托管,也不重建 OpenSandbox 按需运行镜像。需要这两项能力时,部署前分别完成[应用数据升级](../modules/application-hosting.md#部署要求与数据安全)和[浏览器运行镜像检查](../modules/browser-automation.md)。配置不同的 APPLICATION_DB_PASSWORD 与 APPLICATION_OWNER_DB_PASSWORD、共享 Redis 和适合网络环境的 BROWSER_DNS_RESOLVER_URL;使用 application_owner 作为应用数据运行账号。已有数据库卷也必须重新执行 application-database-init,然后更新后端、MCP、前端及沙箱运行镜像,并创建新浏览器会话进行验证。
### WSL 内存紧张时限制构建资源
@@ -254,11 +254,11 @@ make migrate-new msg="describe change"
BUILDX_BUILDER=hugagent-limited COMPOSE_PARALLEL_LIMIT=1 docker compose build mcp
BUILDX_BUILDER=hugagent-limited COMPOSE_PARALLEL_LIMIT=1 docker compose build frontend
docker buildx build --builder hugagent-limited --load \
- -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
+ -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .
docker buildx stop hugagent-limited
builder 已存在时直接复用。构建结束后停止专用 builder,保留缓存供下次使用。
-应用托管部署仍需叠加 docker-compose.application-hosting.yml。
+应用托管部署仍需叠加 docker/application-hosting/compose.application-hosting.yml。
不要同时运行完整构建和多组浏览器测试。WSL 上限应结合其他容器及编辑器的实际占用设置。
## 模型网关启动与排障(商业版 EE)
diff --git a/document/zh-CN/deployment/environment-variables.md b/document/zh-CN/deployment/environment-variables.md
index e83e6ab5..7cdab904 100644
--- a/document/zh-CN/deployment/environment-variables.md
+++ b/document/zh-CN/deployment/environment-variables.md
@@ -23,7 +23,7 @@
| `HOST_REPO_PATH` | 仓库绝对路径 | 宿主机仓库根路径;backend 经 docker.sock 调宿主 daemon 重建沙盒镜像时解析 compose 相对路径用 | CE |
| `DOCKER_GID` | `999` | 宿主 docker 组 GID,授予 backend 容器读写 `/var/run/docker.sock`(`stat -c '%g' /var/run/docker.sock` 查询) | CE |
| `COMPOSE_PROFILES` | CE Docker: `opensandbox` | compose profile 选择(`script_runner` / `opensandbox`,可叠加 `,mem0`),须与 `SANDBOX_PROVIDER` 一致 | CE |
-| `COMPOSE_FILE` | (未设) | 叠加 compose 文件;CubeSandbox 时设 `docker-compose.yml:docker-compose.cube.yml` | CE |
+| `COMPOSE_FILE` | (未设) | 叠加 compose 文件;CubeSandbox 时设 `docker-compose.yml:docker/sandbox/compose.cube.yml` | CE |
| `MAX_REQUEST_SIZE` | `52428800`(代码默认 50 MB) | 后端单请求体上限(字节) | CE |
| `DB_MIGRATION_RETRIES` / `DB_MIGRATION_RETRY_INTERVAL` | `20` / `2`(入口脚本默认) | 启动迁移重试次数 / 间隔秒 | CE |
@@ -57,7 +57,7 @@
| `ENABLE_SUMMARY` | `true` | 关闭则标题回退为消息截断 | CE |
| `SUMMARY_MAX_ROUNDS` | `3` | 超过该轮数后标题不再更新 | CE |
| `OPENAI_API_KEY` / `OPENAI_API_BASE` | (空)/ `https://api.openai.com/v1` | 备用 OpenAI 直连配置(compose 透传) | CE |
-| `ROUTER_STRATEGY` | `main_only`(代码默认) | 路由策略(`orchestration/strategy.py`) | CE |
+| `ROUTER_STRATEGY` | `main_only`(代码默认) | 路由策略(`core/agent_runtime/strategy.py`) | CE |
| `FOLLOWUP_ENABLED` | `true`(代码默认) | 追问建议生成开关 | CE |
## 鉴权与登录
@@ -182,10 +182,11 @@ Config 管理台会分别保存 Dify、FastGPT、WeKnora 的 URL、API Key 与
| `MCP_HOST` | `mcp`(compose 默认) | MCP 容器主机名;本地调试可设 `127.0.0.1` | CE |
| `MCP_MARKET_REVALIDATE_INTERVAL` | `21600` | MCP 市场远程工具快照复检周期(秒,最小 `300`) | CE |
| `MCP_SELF_SERVICE_ALLOW_PRIVATE_NETWORK` | `false` | 是否允许用户自助添加回环、RFC1918 或 IPv6 ULA 地址上的 MCP;仅应在用户可信的私有化环境开启,云元数据/链路本地等高风险地址仍会拦截,私有地址也不能由用户申请上架市场 | CE |
-| `INTERNET_SEARCH_ENGINE` | `tavily`(compose 默认) | 联网搜索引擎:`tavily` / `baidu` / `langsearch` | CE |
+| `INTERNET_SEARCH_ENGINE` | `tavily`(compose 默认) | 联网搜索引擎:`tavily` / `baidu` / `langsearch` / `deepseek` | CE |
| `TAVILY_API_KEY` | (空) | Tavily Search API Key;选择 Tavily 时必填 | CE |
| `BAIDU_API_KEY` | (空) | 百度搜索 API Key;选择百度时必填 | CE |
| `LANGSEARCH_API_KEY` | (空) | LangSearch Web Search API Key;选择 LangSearch 时必填 | CE |
+| `DEEPSEEK_SEARCH_API_KEY` | (空) | 调用 Search API 的 DeepSeek 开放平台 API Key;选择 DeepSeek 时必填 | CE |
| `INTERNET_SEARCH_COUNTRY` / `INTERNET_SEARCH_AUTO_PARAMETERS` | (空) | Tavily 国家排名偏好(仅 general,未配置则不传)/ 自动参数 | CE |
| `QUERY_DATABASE_URL` | `http://your-database-api-host:6200` | 数仓查询工具的 HTTP 后端 | EE(行业工具) |
| `QUERY_DATABASE_TIMEOUT_SECONDS` / `QUERY_DATABASE_RETRY_TIMES` / `QUERY_DATABASE_MAX_OUTPUT_TOKENS` | (空) | 数仓查询调用参数 | EE |
@@ -217,13 +218,13 @@ Config 管理台会分别保存 Dify、FastGPT、WeKnora 的 URL、API Key 与
|---|---|---|
| `OPENSANDBOX_DOMAIN` | `http://opensandbox:8080` | OpenSandbox server 地址 |
| `OPENSANDBOX_API_KEY` | CE 初始化生成 | 控制器与后端使用相同密钥;不在日志中输出 |
-| `OPENSANDBOX_IMAGE` | `hugagent-opensandbox-custom:latest`(compose 默认;代码兜底 `opensandbox/code-interpreter:v1.0.2`) | 沙盒运行时镜像;自建镜像预装项目全部依赖(`docker/Dockerfile.opensandbox`) |
+| `OPENSANDBOX_IMAGE` | `hugagent-opensandbox-custom:latest`(compose 默认;代码兜底 `opensandbox/code-interpreter:v1.0.2`) | 沙盒运行时镜像;自建镜像预装项目全部依赖(`docker/sandbox/Dockerfile.opensandbox`) |
| `OPENSANDBOX_READY_TIMEOUT_S` | `90` | 等沙盒就绪上限(秒) |
| `OPENSANDBOX_REQUEST_TIMEOUT_S` | `120` | 单次 HTTP 调用超时(秒) |
| `OPENSANDBOX_PORT` | 全量仓库 `8910`;CE 不映射 | 控制器容器内固定 8080 |
| `OPENSANDBOX_POOL_JUPYTER_MIN_IDLE` / `MAX_IDLE` | `.env.example` 1/3;compose 默认 2/3 | 用户会话池(历史配置名);CE 最小预热数为 0 |
| `OPENSANDBOX_POOL_LIGHT_MIN_IDLE` / `MAX_IDLE` | `2` / `5` | 轻量桶(一次性执行) |
-| `OPENSANDBOX_POOL_MAX_TOTAL` | CE: `4`; full: `20` | 全池上限(含使用中) |
+| `OPENSANDBOX_POOL_MAX_TOTAL` | CE: `4`; full: `20` | 控制器全局上限(所有 worker、预热与使用中实例);backend 和控制器使用同一值 |
| `OPENSANDBOX_SNAPSHOT_ENABLED` | `true` | 快照持久化总开关(idle 时 snapshot+kill,重连恢复) |
| `OPENSANDBOX_SNAPSHOT_RETENTION_DAYS` | `7` | 快照保留天数(GC 周期清理) |
| `OPENSANDBOX_SNAPSHOT_WAIT_TIMEOUT_S` | `120` | 等快照 Ready 的轮询上限(秒) |
@@ -317,16 +318,10 @@ Config 管理台会分别保存 Dify、FastGPT、WeKnora 的 URL、API Key 与
License 细节见 [License 机制](../editions/license.md)。
-## 其他(限流、熔断、安全、告警)
+## 其他(安全、告警)
| 变量 | 默认值 | 说明 | 版本 |
|---|---|---|---|
-| `RATE_LIMIT_ENABLED` | `true` | 接口限流开关 | CE |
-| `RATE_LIMIT_STORAGE` | `memory://` | 限流计数存储(可 `redis://...`) | CE |
-| `RATE_LIMIT_GLOBAL` / `RATE_LIMIT_PER_USER` | `500/minute` / `50/minute` | 全局 / 单用户限流 | CE |
-| `CB_USER_CENTER_THRESHOLD` / `CB_USER_CENTER_TIMEOUT` | `5` / `60` | 用户中心熔断器 | EE |
-| `CB_MODEL_API_THRESHOLD` / `CB_MODEL_API_TIMEOUT` | `10` / `30` | 模型 API 熔断器 | CE |
-| `CB_STORAGE_THRESHOLD` / `CB_STORAGE_TIMEOUT` | `5` / `60` | 存储熔断器 | CE |
| `CORS_ORIGINS` | compose 默认 `http://localhost:3000,http://localhost:5173` | 允许跨域来源 | CE |
| `ENABLE_SECURITY_HEADERS` | (注释,建议生产 `true`) | 安全响应头 | CE |
| `AUDIT_LOG_RETENTION_DAYS` / `AUDIT_LOG_EXPORT_ENABLED` | `90` / `true`(注释示例) | 审计日志保留 / 导出 | EE |
@@ -340,7 +335,7 @@ License 细节见 [License 机制](../editions/license.md)。
|---|---|
| 变量样例(部署起点) | `.env.example` |
| 集中式设置读取(唯一入口) | `src/backend/core/config/settings.py` |
-| compose 层注入与默认值 | `docker-compose.yml`、`docker-compose.cube.yml` |
+| compose 层注入与默认值 | `docker-compose.yml`、`docker/sandbox/compose.cube.yml` |
| 沙箱 provider 选择 | `src/backend/core/sandbox/`、`settings.py::SandboxSettings` |
| 记忆设置 | `settings.py::MemorySettings`、`src/backend/core/memory/`(service.py / pipeline.py) |
| 问题反馈 GitHub 配置 | `src/backend/edition_ee/services/system_config.py`、`feedback_service.py` |
@@ -389,7 +384,7 @@ License 细节见 [License 机制](../editions/license.md)。
模块开启后,社区版已存在的登录用户即可使用。企业版额外检查 Config 后台的用户、角色或团队 **Personal Agent** 权限(默认关闭);收回权限会隐藏入口,并停止排队、定时触发和渠道转交任务。不需要 persistent_sandbox License。两个版本都保留对象归属检查和操作批准。
-请阅读 [Personal Agent](../modules/personal-agent.md)。部署时使用根 Compose 文件、docker/compose.personal-agent.yml 和 personal-agent profile。
+请阅读 [Personal Agent](../modules/personal-agent.md)。部署时使用根 Compose 文件、docker/personal-agent/compose.personal-agent.yml 和 personal-agent profile。
diff --git a/document/zh-CN/deployment/offline-production.md b/document/zh-CN/deployment/offline-production.md
deleted file mode 100644
index db13e3e6..00000000
--- a/document/zh-CN/deployment/offline-production.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# 离线生产部署(商业版 EE)
-
-> 最后更新:2026-10-16 | [English](../../en/deployment/offline-production.md) | 返回 [部署指南](README.md)
-
-离线交付使用 `deploy` 分支的编译加固镜像及配套部署 kit。
-构建和打包在供应方的独立 `deploy` 工作区执行;目标机加载镜像并使用同批 kit 启动。
-构建、分支同步、上传和生产发布分别遵守项目授权规则。
-
-## 联网侧:准备完整交付包
-
-1. 核对已验证的 `deploy` 提交、干净工作区、产品身份及目标 CPU 架构。
-2. 完整读取该提交的 `deploy_kit/save_pack_ee.sh` 和配套 Compose 文件。
-3. 构建并打包:
-
- ```bash
- # 在 deploy 分支的工作区根目录执行
- bash deploy_kit/save_pack_ee.sh --build
- # 编译镜像已存在且已核对本次提交时,可直接打包
- bash deploy_kit/save_pack_ee.sh
- ```
-
-脚本校验编译后端、MCP、前端及必备基础设施镜像,并按本机存在情况加入可选镜像。
-交付方还须核对目标 profiles 的全部依赖,不能把“可选镜像未安装”当作目标机不需要它。
-镜像名、版本、输出目录与文件名以该提交的脚本和 manifest 为准,不手工改名。
-交付包同时包含镜像归档、manifest 和匹配的部署 kit;License 单独安全交付。
-不得用普通源码镜像或只包含后端和前端的增量包替代完整 EE 交付包。
-
-## 目标侧:加载与上线
-
-按交付 kit 的说明配置独立项目名、数据目录、License 和必要的受保护环境文件。
-升级前备份数据库与文件,确认镜像 manifest、目标架构和所选 profiles 一致。
-授权后加载指定镜像归档,并使用同批 kit 的启动入口及完整 Compose 覆盖文件。
-目标机不能联网拉镜像时,必须先确认所需镜像已全部加载,启动不得触发现场构建。
-不使用旧源码部署脚本,也不自动选取目录里时间最新但未经核验的归档。
-
-验收至少包括 `/health`、`/ready`、登录、附件、模型调用及实际启用的沙箱能力。
-数据库迁移会改变 schema;数据卷持久化不代表升级不会修改业务数据。
-失败时按交付记录中的镜像、配置和数据备份恢复,不删除数据卷。
-
-## 提示词快照随包带入
-
-系统提示词存在数据库 `content_blocks` 表(`prompt_versions` 版本池 + `prompt_hub` 提示词广场),**不进仓库、不在镜像里**。要把新提示词推上生产,需作为单独数据文件随镜像包带入并在生产侧导入:
-
-### 1. 联网侧导出
-
-```bash
-python src/backend/scripts/export_content.py \
- --api-url http://localhost:3001 --only prompts
-# 产出 src/backend/scripts/exported/prompts_snapshot_.json
-```
-
-(也支持 `--database-url` 直连库导出;`ADMIN_TOKEN` 自动从 `.env` 读取。)
-
-### 2. 拷贝
-
-把 `prompts_snapshot_.json` 与镜像 tarball 一起拷到生产机。
-
-### 3. 生产侧导入
-
-前提:生产 backend 镜像须已包含 `POST /v1/content/prompts/import` 接口(`src/backend/api/routes/v1/content.py`)。在新 backend 起好后:
-
-```bash
-BACKEND_CONTAINER=your-backend-container
-docker cp prompts_snapshot_.json "${BACKEND_CONTAINER}:/tmp/"
-docker exec "$BACKEND_CONTAINER" curl -sS -X POST \
- 'http://localhost:3001/v1/content/prompts/import?overwrite=true' \
- -H "Authorization: Bearer <生产ADMIN_TOKEN>" \
- -H 'Content-Type: application/json' \
- -d @/tmp/prompts_snapshot_.json
-```
-
-API 导入会自动失效提示词缓存,**无需重启 backend**。
-
-> 这是按需的一次性数据操作:DB 卷持久化,不必每次发版都导提示词——只在要把新提示词推上生产时执行。
-
-## 相关源码
-
-| 功能 | 文件 |
-|---|---|
-| EE 完整镜像包(deploy 分支) | `deploy_kit/save_pack_ee.sh` |
-| EE 编译镜像(deploy 分支) | `docker/Dockerfile.backend-compiled`、`docker/Dockerfile.mcp-compiled` |
-| Compose 覆盖(deploy 分支) | `docker-compose.prod.yml`、`docker-compose.deploy.yml`;运行时以交付 kit 为准 |
-| 提示词导出 / 导入 | `src/backend/scripts/export_content.py`、`src/backend/scripts/import_content.py` |
-| 提示词版本池 | `src/backend/core/services/prompt_version_service.py` |
diff --git a/document/zh-CN/deployment/quick-install.md b/document/zh-CN/deployment/quick-install.md
index 961e6099..fd9dbcb3 100644
--- a/document/zh-CN/deployment/quick-install.md
+++ b/document/zh-CN/deployment/quick-install.md
@@ -42,7 +42,7 @@ curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh
3. Linux 检测并安装 `bubblewrap`,macOS 检查系统 `sandbox-exec`;沙箱后端不可用时安装失败,不会降级为裸执行;
4. 检测可选的 LibreOffice;缺失时说明不可用能力并询问是否立即安装,跳过或安装失败不会阻断其余功能;
5. 在 `~/.hugagent/venv` 创建虚拟环境(检测到 [uv](https://github.com/astral-sh/uv) 时使用 uv,否则使用 `python -m venv`),并自动重建上次中断留下的不完整环境;
-6. 安装 `requirements.txt`、`hugagent` 控制台命令、内置 Agent Skills 的 Python/Node.js 依赖,以及可选的本地知识库依赖;
+6. 安装 `requirements.txt`、`hugagent-server` 控制台命令、内置 Agent Skills 的 Python/Node.js 依赖,以及可选的本地知识库依赖;
7. 把前端构建到 `src/frontend/dist`;
8. 进入交互式首次配置向导。
@@ -73,14 +73,14 @@ curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh
**第 4 步(可选)· 配置文件解析服务**——上传 PDF / 扫描件解析需要一个外部解析服务(MinerU 兼容),填入其 API URL 即可(写入 `file_parser.api_url`);直接回车跳过。Excel / CSV / PPTX / 文本为进程内解析,无需此项。
**第 5 步(可选)· 配置互联网搜索**——智能体联网检索需要一个搜索引擎
-密钥。选择 `tavily`(默认)、`baidu`(千帆 AppBuilder)或 `langsearch`,
+密钥。选择 `tavily`(默认)、`baidu`(千帆 AppBuilder)、`langsearch` 或 `deepseek`,
然后填写所选引擎对应的 API Key;直接回车可跳过。事后也可在网页
「设置 → 系统管理 → 服务配置」里补配。
引导末尾会打印**本机能力概览**(Node.js / pandoc / LibreOffice 是否就绪,对应 React 建站 / Word 转换 / PPT 和 Word 在线预览),随后自动起服务并打开 `http://127.0.0.1:3001/`。
> **警告:** 服务默认只监听 `127.0.0.1`。若服务器确实需要接受远程连接,请使用
-> `hugagent serve --host 0.0.0.0 --port 3001 --no-browser`,并先配置强管理员密码、
+> `hugagent-server serve --host 0.0.0.0 --port 3001 --no-browser`,并先配置强管理员密码、
> 防火墙和 HTTPS。不要在不受信任的网络中直接暴露服务。
### 非交互安装(自动化 / CI)
@@ -88,7 +88,7 @@ curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh
`onboard` 支持旁路参数,用于脚本化安装:
```bash
-hugagent onboard \
+hugagent-server onboard \
--username admin --password '<强密码>' \
--model-base-url https://api.deepseek.com/v1 \
--model-api-key '' --model-name deepseek-chat \
@@ -113,10 +113,10 @@ hugagent onboard \
## 日常使用
```bash
-hugagent # 已初始化 → 起服务并打开浏览器;未初始化 → 自动进引导
-hugagent serve # 显式起服务(--host 改监听地址,--port 改端口,--no-browser 不开浏览器)
-hugagent onboard # 重跑引导 / 改配置
-hugagent doctor # 环境自检(Python 版本、端口占用、数据目录、前端构建、依赖等)
+hugagent-server # 已初始化 → 起服务并打开浏览器;未初始化 → 自动进引导
+hugagent-server serve # 显式起服务(--host 改监听地址,--port 改端口,--no-browser 不开浏览器)
+hugagent-server onboard # 重跑引导 / 改配置
+hugagent-server doctor # 环境自检(Python 版本、端口占用、数据目录、前端构建、依赖等)
```
## 数据目录
@@ -168,16 +168,16 @@ hugagent doctor # 环境自检(Python 版本、端口占用、数据目录
| 现象 | 处理 |
|---|---|
-| 起服务报端口被占用 | `hugagent serve --port <其他端口>`;或先 `hugagent doctor` 查占用 |
+| 起服务报端口被占用 | `hugagent-server serve --port <其他端口>`;或先 `hugagent-server doctor` 查占用 |
| 网页打开是一段 JSON 而非应用 | 前端未构建:`cd src/frontend && npm run build`,或设 `FRONTEND_DIST_DIR` 指向已构建的 `dist` |
-| 登录报模型不可用 | 重跑 `hugagent onboard` 重配模型(引导会实测连通性) |
+| 登录报模型不可用 | 重跑 `hugagent-server onboard` 重配模型(引导会实测连通性) |
| 启动日志反复出现 `AllocTimestamp` / `Method not implemented` | 停止当前服务并重新运行公开一键安装器。安装器会把 PyMilvus 与 Milvus Lite 校准到兼容版本,不会删除 `~/.hugagent/milvus.db`。 |
-| 想换模型 / 改配置 | 重跑 `hugagent onboard`,或登录后到「设置 → 系统管理 → 模型服务 / 服务配置」调整 |
+| 想换模型 / 改配置 | 重跑 `hugagent-server onboard`,或登录后到「设置 → 系统管理 → 模型服务 / 服务配置」调整 |
| PPT/Word 预览提示 LibreOffice 未安装 | 重新运行一键安装器并在提示时选择安装;Debian/Ubuntu 也可执行 `sudo apt-get update && sudo apt-get install -y libreoffice-impress libreoffice-writer libreoffice-calc`,然后重启 HugAgentOS |
| bash 提示缺少 OS 沙箱运行器 | Linux 安装 `bubblewrap`(Debian/Ubuntu:`sudo apt-get install bubblewrap`)后重启。装上之前,除「完全放开」外的权限档会直接拒绝执行本机命令——这是设计如此,不会退化成无隔离运行 |
| bash 提示当前系统无法执行这条策略 | 说明这一档要求的隔离超出了本系统能力,例如 Windows 的低完整性沙箱无法限制读取范围、也无法在不提权的情况下断网。按提示放宽对应设置(如把「网络」类别从「阻止」改回「确认」),或换到能提供该能力的系统 |
-| 技能执行反复出现 `fork: Resource temporarily unavailable` | 停止当前服务,重新运行公开安装器完成升级,再启动 `hugagent`。旧版本若留下子进程,先检查当前用户的进程,必要时注销当前登录会话后重试。 |
-| 环境是否就绪 | `hugagent doctor` 一次性自检 |
+| 技能执行反复出现 `fork: Resource temporarily unavailable` | 停止当前服务,重新运行公开安装器完成升级,再启动 `hugagent-server`。旧版本若留下子进程,先检查当前用户的进程,必要时注销当前登录会话后重试。 |
+| 环境是否就绪 | `hugagent-server doctor` 一次性自检 |
## 相关源码
@@ -187,7 +187,7 @@ hugagent doctor # 环境自检(Python 版本、端口占用、数据目录
| CLI(onboard / serve / doctor) | `src/backend/cli.py` |
| 本地模式开关 | `src/backend/core/config/settings.py`(`DeploySettings`,`DEPLOY_PROFILE=local`) |
| 前端静态托管 + `/api` 桥接 | `src/backend/api/local_hosting.py` |
-| MCP / 沙箱子进程督管 | `src/backend/orchestration/local_subprocess.py` |
-| 进程内事件流 / 短时状态 | `src/backend/orchestration/run_event_stream.py`、`src/backend/core/infra/ephemeral.py`(`REDIS_URL` 留空即启用) |
-| 内置 MCP 目录种子 | `src/backend/core/services/mcp_service.py`(`seed_builtin_mcp_servers_if_empty`) |
+| MCP / 沙箱子进程督管 | `src/backend/core/infra/local_runtime/` |
+| 进程内事件流 / 短时状态 | `src/backend/core/runs/events/`、`src/backend/core/infra/ephemeral.py`(`REDIS_URL` 留空即启用) |
+| 内置 MCP 目录种子 | `src/backend/core/capabilities/mcp/service.py`(`seed_builtin_mcp_servers_if_empty`) |
| 环境变量参考 | [environment-variables.md](environment-variables.md)(「无 Docker 本地模式」段) |
diff --git a/document/zh-CN/deployment/space-files.md b/document/zh-CN/deployment/space-files.md
index ea24a7cc..571ec0cd 100644
--- a/document/zh-CN/deployment/space-files.md
+++ b/document/zh-CN/deployment/space-files.md
@@ -31,19 +31,11 @@ S3 服务部署在本机时,请使用容器可达的服务地址。
控制器不再持有 FUSE 连接,无需共享传播目录或宿主挂载准备脚本。
沙盒文件操作仍使用普通用户。
-## 文件导入
+## 存量文件迁移
-先备份数据库和对象数据。暂停对目标目录的写入。
-对每个用户或团队生成导入清单:
-
-```bash
-PYTHONPATH=src/backend python -m scripts.import_space_files --user USER_ID
-PYTHONPATH=src/backend python -m scripts.import_space_files --team TEAM_ID
-```
-
-确认目标目录和文件清单后,追加 `--apply` 执行导入。
-工具拒绝同名冲突,并校验复制后的内容。
-工具保留源对象和文件引用。导入中断后可重新运行。
+独立文件导入命令已移除。迁移存量文件前,先备份数据库和对象数据,
+暂停目标目录写入,制定并审核迁移方案。核对目标路径、同名冲突和文件内容,
+保留源对象及文件引用。
## 验证
@@ -52,20 +44,16 @@ PYTHONPATH=src/backend python -m scripts.import_space_files --team TEAM_ID
撤回团队权限。确认该用户无法访问该团队目录。
验证成功后恢复用户操作。
+## 限制
-## 验证范围
-
-S3 挂载通过本地独立 MinIO、控制器、沙盒和浏览器验证。
-测试覆盖上传、下载、目录列表、只读写入拒绝、目录隔离和挂载撤销。
-OSS 和 OBS 适配尚需在各自的测试桶中验证。
-对象存储目录移动包含多个对象操作。它不是原子的文件系统重命名。
+对象存储目录移动包含多个对象操作,不是原子的文件系统重命名。
OBS 的 V4 配置依据[华为云说明](https://support.huaweicloud.com/obs_faq/obs_faq_0274.html)。
OSS 的 S3 V2 签名依据[阿里云说明](https://www.alibabacloud.com/help/tc/oss/user-guide/0002-00000307)。
## 本机 S3 服务(CE / EE)
-将 `docker/compose.object-storage.yml` 加入 `COMPOSE_FILE`。
+将 `docker/object-storage/compose.object-storage.yml` 加入 `COMPOSE_FILE`。
该服务使用独立数据卷。它不发布宿主端口,也不使用 Milvus 的数据卷。
配置 `S3_ENDPOINT=http://object-storage:9000` 和 `S3_BUCKET=hugagent-files`。
分别生成随机 `S3_ACCESS_KEY` 与 `S3_SECRET_KEY`,写入权限为 600 的环境文件。
@@ -122,4 +110,4 @@ MCP 接口位于 `src/backend/extensions/mcp_servers/space_files_mcp/`。
缺失、过期或参数变更的票据不能发出网关请求。
桌面升级需同时交付新版内置后端和云端后端,并重新同步能力清单。
-对象存储镜像使用 MinIO 官方 GitHub Release 中固定 SHA-256 的二进制文件。Docker 通过 docker/Dockerfile.object-storage 构建此镜像。已有 Milvus 镜像和数据卷保持独立。
+对象存储镜像使用 MinIO 官方 GitHub Release 中固定 SHA-256 的二进制文件。Docker 通过 docker/object-storage/Dockerfile.object-storage 构建此镜像。已有 Milvus 镜像和数据卷保持独立。
diff --git a/document/zh-CN/deployment/uos1070-desktop.md b/document/zh-CN/deployment/uos1070-desktop.md
deleted file mode 100644
index ea0c741e..00000000
--- a/document/zh-CN/deployment/uos1070-desktop.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# UOS 统信 1070 aarch64 桌面端
-
-> [English](../../en/deployment/uos1070-desktop.md)
-
-UOS 1070 aarch64 使用仓库中的 `desktop-uos/` Electron 43 发行线。交付格式固定为
-Debian ARM64 `.deb`;它不依赖系统 WebKitGTK 4.1,因此不受 Tauri 2 在 Debian 10 / UOS
-1070 上的 WebView 依赖阻断。
-
-## 支持范围
-
-| 项目 | 生产基线 |
-|---|---|
-| 操作系统 | UOS 1070 桌面版,aarch64 |
-| libc | glibc 2.28 |
-| 桌面壳 | Electron 43.4.1 |
-| 安装包 | Debian `arm64` `.deb` |
-| 本机运行时 | 私有 CPython 3.11,`aarch64-manylinux_2_28` 锁 |
-| 数据目录 | `~/.hugagent` |
-| 配置目录 | `~/.config/com.hugagent.desktop` |
-
-完整包支持本机、云端和双模式,能力与现有桌面端一致:deep-link 登录、回环反代、流式
-对话、托盘/快捷问答、原生通知、本地文件夹项目、双上游路由、身份与模型配置桥接、离线
-本机服务安装/升级/回滚、SHA-256 验证更新。
-
-UOS 默认隐藏窗口内菜单栏,保留系统标题栏与窗口控制。托盘的“文件、编辑、视图、帮助”
-提供完整桌面命令;Ctrl+Shift+N 新建窗口、Ctrl+N 新建对话继续可用。工作区与 macOS
-共用模块导航渐变、内层侧栏与内容圆角、浅深主题和加载布局;快速问答保持原有紧凑布局。
-UOS 的原生标题栏位于网页区域外,因此不再额外占用网页顶部安全区。
-
-发行品牌可以为完整包预设首启模式;固定双模式的发行版会直接进入一键初始化。初始化与
-安装页使用与现有桌面端一致的光晕、轨道和进度高光动画,只显示阶段、百分比与可操作错误,
-不在页面底部展示原始日志。日志仍保存在本机服务日志目录,便于排障。
-
-## 构建与验证
-
-完整包只能在原生 Linux aarch64 构建机生成,推荐直接使用 UOS 1070 构建机。x86_64
-机器只能交叉生成不含 Python 运行时的精简包。
-
-```bash
-cd desktop-uos
-npm install
-npm test
-npm run build
-npm run verify:deb -- "dist/HugAgentOS UOS_0.5.15_uos1070_arm64.deb"
-```
-
-验收时至少检查:
-
-1. `dpkg-deb --field <包> Architecture` 返回 `arm64`;
-2. Electron 主 ELF 的最高 GLIBC 引用不超过 2.28;
-3. 断网冷启动完整包,本机服务安装阶段没有 `pip install`、uv 或 Python 下载;
-4. 本机、云端和双模式分别完成登录、SSE 对话、附件上传下载、文件夹选择和通知;
-5. 双模式下云端项目进云端,本地文件夹项目进本机;
-6. 使用测试版更新清单完成 SHA-256 校验、系统授权安装和重启。
-
-UOS 与 Windows、macOS 共用同一个发布索引:客户端按自己的平台取更新,某个平台还没发包
-时明确提示「当前平台暂无可用更新」,而不带平台参数的旧客户端读到的共同版本要等该版本
-三个平台都发齐才前进。桌面端版本号三平台保持一致,构建时会校验。
-
-具体命令、产物和发布步骤见 [`desktop-uos/README.md`](../../../desktop-uos/README.md)。
-
-> Electron 43 官方支持计划在 2027 年 1 月结束。它是 UOS 1070 的当前兼容基线,生产
-> 维护仍需跟进最新 43.x 补丁,并持续验证 Electron 44+ 后续迁移。
-
-## 与当前 Tauri 功能同步
-
-UOS 复用当前源码中的 React 前端与 CE 本机后端。重新构建完整包会带入最新能力来源、
-本机启停、技能同步和工作目录逻辑;只更新云端不会替换已安装客户端的这些资源。
-
-- “文件”菜单支持打开文件夹并进入本机项目对话;普通包可重新选择运行模式。
-- “视图”提供放大、缩小、实际大小(Ctrl + +/-/0,或 Ctrl+滚轮)。缩放档位与 Tauri
- 一致,保存在 `prefs.json`,新窗口和重启沿用;系统 DPI 保持原生处理。
-- “帮助”、托盘和侧栏共用更新流程。后台订阅发布通知,仅提示新版;确认后显示下载进度,
- 下载阶段可取消。安装仍校验 Ed25519 签名和 SHA-256,并由 UOS 系统授权。
- 旧服务器使用低频检查,断线退避重连;并发检查不会重复安装。
-- 回环反代在注入会话和桥接凭据前检查 Origin/Fetch Metadata;不带来源的直接请求被拒绝。
-- 服务日志运行中轮转,每份最大 32 MiB,只保留当前和上一份。能力未就绪时的检查间隔
- 从 500 ms 逐步增加至 15 秒。升级保留原 UOS 数据及能力目录,不自动迁移已有配置。
-
-完整包还支持与 Tauri 相同的仅混合模式构建入口:
-
-```bash
-JX_DEFAULT_SERVER_BASE=https://agent.example.com npm run build:hybrid-only
-```
-
-该入口固定云端地址与双模式,必须提供云端地址;精简包不能用于仅混合模式。
-完整 ARM64 离线包仍须在原生 aarch64 构建机完成构建和安装验收。
diff --git a/document/zh-CN/development/backend.md b/document/zh-CN/development/backend.md
index d8c53c55..8e8c4b21 100644
--- a/document/zh-CN/development/backend.md
+++ b/document/zh-CN/development/backend.md
@@ -41,7 +41,7 @@ docker-compose logs -f backend
| `make migrate` | `alembic upgrade head` |
| `make migrate-new msg="..."` | `alembic revision --autogenerate -m "..."` |
| `make migrate-down` / `migrate-history` | 回滚一步 / 查看历史 |
-| `make db-reset` / `db-seed` | 重置(破坏性)/ 种子数据 |
+| `make db-reset` | 重置数据库(破坏性) |
| `make build` / `up` / `down` / `logs` / `ps` 等 | docker-compose 包装 |
> ⚠️ 已提交代码并非 formatter-clean:**不要对未改动的文件整体跑 black/isort**(会把真实 diff 淹没在格式化噪音里),只格式化自己改的范围。
@@ -68,10 +68,10 @@ PYTHONPATH=src/backend python -m pytest src/backend/tests/memory -q
## Alembic 迁移流程
-1. 在 `src/backend/core/db/models.py` 改 ORM 模型;
+1. 在 `src/backend/core/db/models/` 改 ORM 模型;
2. `make migrate-new msg="add xxx table"` 自动生成迁移(**人工审阅生成结果**,autogenerate 不可全信);
3. `make migrate`(或容器内 `alembic upgrade head`)应用;
-4. 若是 **EE 专属表**:把表名加进 `core/db/edition_tables.py::EE_ONLY_TABLES`(CE 不建 EE 空表,集合内表名有存在性断言)。
+4. 若是 **EE 专属表**:把表名加进 `edition_ee/db/edition_tables.py::EE_ONLY_TABLES`(CE 不建 EE 空表,集合内表名有存在性断言)。
注意:主仓 alembic 链是 EE 链;CE 派生树用独立的 `ce_0001` 基线链(见 [CE 构建管线](../editions/build-ce.md#ce-数据库差异)),主仓新增迁移不会进入 CE。
@@ -175,7 +175,7 @@ PYTHONPATH=src/backend python -m extensions.mcp_servers._mcp._selftest
## 新增技能(Agent Skill)步骤
-技能加载是多源架构(`core/agent_skills/config.py`):内置(`extensions/skills/default/`,always-on)、admin(DB / `/app/storage/admin_skills/`)、用户、项目四个加载源按优先级合并;`extensions/skills/marketplace/` 是安装制技能市场种子,由 marketplace 服务单独扫描,**不在默认加载源内**。
+技能加载是多源架构(`core/capabilities/skills/config.py`):内置(`extensions/skills/default/`,always-on)、admin(DB / `/app/storage/admin_skills/`)、用户、项目四个加载源按优先级合并;`extensions/skills/marketplace/` 是安装制技能市场种子,由 marketplace 服务单独扫描,**不在默认加载源内**。
新增**内置技能**:
@@ -188,7 +188,7 @@ PYTHONPATH=src/backend python -m extensions.mcp_servers._mcp._selftest
## 新功能检查清单
-- [ ] ORM 模型 + 索引 + 时间戳(`core/db/models.py`),EE 表已加 `EE_ONLY_TABLES`
+- [ ] ORM 模型 + 索引 + 时间戳(`core/db/models/`),EE 表已加 `EE_ONLY_TABLES`
- [ ] Repository 过滤软删除;Service 承载业务与权限
- [ ] 路由用信封响应、异常走 `core/infra/exceptions`
- [ ] 路由已登记 `CE_ROUTERS` / `EE_ROUTERS`(EE 配能力位 + manifest 排除)
@@ -205,10 +205,10 @@ PYTHONPATH=src/backend python -m extensions.mcp_servers._mcp._selftest
| 响应信封 | `src/backend/core/infra/responses.py` |
| 异常体系 | `src/backend/core/infra/exceptions.py` |
| 依赖注入 | `src/backend/api/deps.py` |
-| ORM / 建表边界 | `src/backend/core/db/models.py`、`src/backend/core/db/edition_tables.py` |
+| ORM / 建表边界 | `src/backend/core/db/models/`、`src/backend/edition_ee/db/edition_tables.py` |
| MCP 端口表 / 连接配置 | `src/backend/extensions/mcp_servers/_ports.py`、`src/backend/core/config/mcp_config.py` |
| 能力目录 | `src/backend/core/config/catalog.json` |
-| 技能加载 | `src/backend/core/agent_skills/`(`config.py` / `loader.py` / `registry.py`) |
-| 工作流编排 | `src/backend/orchestration/workflow.py` |
+| 技能加载 | `src/backend/core/capabilities/skills/`(`config.py` / `loader.py` / `registry.py`) |
+| 工作流编排 | `src/backend/core/agent_runtime/runner.py` |
相关阅读:[前端开发指南](frontend.md) · [API 总览](../api/overview.md) · [MCP 工具](../modules/mcp-tools.md) · [技能系统](../modules/agent-skills.md)
diff --git a/document/zh-CN/development/container-layout.md b/document/zh-CN/development/container-layout.md
deleted file mode 100644
index 3eb59e90..00000000
--- a/document/zh-CN/development/container-layout.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# 容器响应式布局
-
-聊天工具栏跟随所在面板的可用宽度调整,首页、对话页、项目页和分屏共用同一套布局。
-
-- 工具区与模型/发送区分组,空间不足时自然换行。
-- 标签逐级收起,保留图标、可访问名称和模式退出按钮;模型名称保留并省略长文本。
-- 触屏使用真实的 44px 点击区域,参与排版,不叠加透明命中层。
-- 项目、能力目录和知识库卡片按可用宽度自动排列,长名称不会撑破网格。
-- 系统安全区、页面高度与弹出菜单仍按其实际视口处理。
-
-实现入口:前端的 components/chat/ComposerToolbar.tsx、styles/chat/toolbar-layout.css、
-styles/projects.css 和 styles/catalog/。容器边界负责提供宽度,后代负责响应;
-不要让元素查询自身,也不要通过 JavaScript 重复维护 CSS 断点。
-
-在 WSL 项目目录进入 src/frontend,使用项目支持的 Node.js 版本执行:
-
- npm run test:container-layout-browser
- npm run test:canvas-layout-browser
- npm run test:chat-width-browser
- npm run lint
- npm run build
-
-新增用例覆盖固定大窗口中的窄容器、手机窗口、分数像素边界、深浅主题、
-长名称、全部模式、触屏目标、键盘退出及发送/中止。
diff --git a/document/zh-CN/development/frontend.md b/document/zh-CN/development/frontend.md
index 93e5c6cf..9c2a8d93 100644
--- a/document/zh-CN/development/frontend.md
+++ b/document/zh-CN/development/frontend.md
@@ -132,6 +132,13 @@ const multiTenancy = useEditionStore((s) => (s.loaded ? !!s.features.multi_tenan
- 新增 EE 界面区块时:① 用上面的选择器按对应能力位门控;② 若整个页面属 EE(如 /admin、/config),无需 runtime 门控——CE 派生树直接物理排除组件目录并用 overlay 的 `main.tsx` 摘掉路由(见 [CE 构建管线](../editions/build-ce.md));
- 处理接口报错时记得识别 `LicenseError`(402),给出「联系管理员更新 license」而非笼统失败提示。
+## 容器响应式布局
+
+聊天工具栏、项目 / 能力目录 / 知识库卡片按所在面板的可用宽度排版,首页、对话页、项目页和分屏共用同一套规则:
+容器提供宽度,后代按容器响应;不要让元素查询自身,也不要用 JavaScript 重复维护 CSS 断点。触屏点击区域为真实的 44px。
+入口:`components/chat/ComposerToolbar.tsx`、`styles/chat/toolbar-layout.css`、`styles/projects.css`、`styles/catalog/`。
+修改后运行 `npm run test:container-layout-browser`、`npm run test:chat-width-browser`。
+
## 新功能检查清单
- [ ] 组件 PascalCase + named export + Props 接口
diff --git a/document/zh-CN/development/workbench-migration.md b/document/zh-CN/development/workbench-migration.md
index f1593a59..5585d389 100644
--- a/document/zh-CN/development/workbench-migration.md
+++ b/document/zh-CN/development/workbench-migration.md
@@ -44,24 +44,7 @@ SDK CLI 只校验声明,不迁移数据库或业务数据。
# 工作台配置迁移与恢复
-迁移是显式运维操作,不会随启动自动执行。先在隔离数据库副本完成演练;真实配置迁移需要单独授权。当前命令只接受本机隔离副本。
-
-## 盘点与切换
-
-1. 停止副本的所有写入,包括内容导入、用户偏好、插件安装和后台写入。--writes-frozen 只是操作员确认,不会自动停止服务。
-2. 配置 WB20_COPY_DATABASE_URL。SQLite 文件须在当前源码目录的 .scratch/workbench-20 下。PostgreSQL 须为回环主机且数据库名以 workbench_20 开头。不要将真实库设置为演练目标。
-3. 创建权限为 0700 的快照目录。运行如下命令:
-
-```bash
-PYTHONPATH=src/backend python -m scripts.workbench_migration inventory \
- --snapshot /absolute/private/path/before.json
-PYTHONPATH=src/backend python -m scripts.workbench_migration apply \
- --snapshot /absolute/private/path/before.json --digest --writes-frozen
-```
-
-盘点不写数据库。报告包含数量、来源版本、脱敏位置和错误码;不打印 URL、用户标识、票据或秘密。完整迁移范围快照保存在 0600 文件中,包含原配置、偏好、角色/团队关系、插件声明和工作台数据。它不是业务文件或完整数据库备份,须与完整数据库/文件备份共同保管。
-
-摘要匹配且源未变化时,切换在单一事务中执行。失败回滚事务。切换前后快照的摘要记录在 workbench_migration:v1 receipt 中。原始 app_config 行和旧导航列表移除;新目录只读取规范来源。重复外部 ID 的全部副本、未知 ID、危险 URL 和未知字段隔离,不产生入口或授权。原始副本保留在受保护快照。
+独立迁移命令已移除。以下内容记录内部迁移与恢复约束,不构成可直接执行的运维流程。现有部署需要迁移时,应先备份并制定针对目标版本的方案,在隔离副本验证后单独授权执行。
## 保留的语义
@@ -69,7 +52,7 @@ PYTHONPATH=src/backend python -m scripts.workbench_migration apply \
- 外部条目顺序和启停保留。默认侧栏转换为策略。已有现代偏好优先;已确认旧 preferences.pinned_apps/app_order 转换为 web 偏好。null 继承,空列表明确取消固定。聊天排序、主题和其他偏好保持不变。
- 无法映射的权限引用保留原值并报告,不成为新授权。旧插件 UI v1/v2 不改写为 v3。不会按名称或 slug 绑定新安装。
- 旧认证配置 workbench_external_auth:legacy: 保持原 key。缺少目标侧适配时报告 auth_configuration_required。管理员须显式审核登录方式;真实旧目标尚未验证时,不得宣称外部登录无回归。不得恢复通用 SSO token 拼接。
-- 同服务恢复保留 workbench_origin_identity。克隆到另一服务须先在克隆副本重新盘点,再执行 clone-origin;旧插件 key 不自动映射为新来源授权。跨来源偏好和数据须另行核对。
+- 同服务恢复保留 workbench_origin_identity。克隆到另一服务须先在克隆副本重新盘点,再重建服务来源标识;旧插件 key 不自动映射为新来源授权。跨来源偏好和数据须另行核对。
## 对外版本窗口
@@ -77,13 +60,7 @@ content-v1 接口在工作台协议 1.x 内保留,协议 2.0 迁移前另行
## 恢复与新增数据
-先冻结全部写入,再执行:
-
-```bash
-PYTHONPATH=src/backend python -m scripts.workbench_migration rollback \
- --snapshot /absolute/private/path/before.json --digest \
- --delta /absolute/private/path/delta.json --writes-frozen
-```
+恢复前冻结全部写入,并按已审核的恢复方案操作。
恢复前导出受保护增量。已映射普通旧外链的修改可回放到旧格式。新身份、应用状态、资源、权限、插件、认证或策略变化不能安全表达时,返回 delta_not_representable 并保留新数据。不要仅恢复旧快照。继续运行已验证的新版本,核对增量并另行制定迁移方案。恢复 receipt 幂等;已恢复批次不能直接重复 apply,应创建新的审核批次。
diff --git a/document/zh-CN/development/workbench-routing.md b/document/zh-CN/development/workbench-routing.md
index 2c558825..40ab74c9 100644
--- a/document/zh-CN/development/workbench-routing.md
+++ b/document/zh-CN/development/workbench-routing.md
@@ -10,6 +10,6 @@
一个应用可以在二级视图中引用自己的 workspace 模块。启动接口只接受主模块及该应用声明的二级模块,不接受安装中其他未声明模块。
-此功能没有数据库结构迁移。旧 UI v1/v2、托管应用接口和旧业务地址保留原有契约。完整多平台端到端验证由集成验收报告记录;专项夹具通过不表示原生客户端已通过。
+此功能没有数据库结构迁移。旧 UI v1/v2、托管应用接口和旧业务地址保留原有契约。
目录为空时保留深链接并显示空状态。准确应用查询的网络或服务器故障显示重试,不误报为无权限或不存在。
diff --git a/document/zh-CN/editions/build-ce.md b/document/zh-CN/editions/build-ce.md
index ecd3efbb..966063fa 100644
--- a/document/zh-CN/editions/build-ce.md
+++ b/document/zh-CN/editions/build-ce.md
@@ -26,7 +26,7 @@ manifest 按处理顺序分为以下几段:
glob 模式(相对仓库根),命中即不拷贝。覆盖:
-- **后端 EE 模块**:完整的 `edition_ee/**` 实现根(团队/RBAC、SSO、License 验签与闸门、EE ORM、Dify 集成),以及云存储(`core/storage/s3.py`、`oss.py`)、Cube 沙箱 provider、记忆审计、技能蒸馏等 EE 服务;
+- **后端 EE 模块**:完整的 `edition_ee/**` 实现根(团队/RBAC、SSO、License 验签与闸门、EE ORM、Dify 集成),以及云存储(`core/files/storage/s3.py`、`oss.py`)、Cube 沙箱 provider、记忆审计、技能蒸馏等 EE 服务;
- **EE 路由**:`api/routes/v1/admin_*.py`、`config_*.py`、`audit.py`、`auth.py`、`team_files.py`、`service_configs.py`、`data_sources.py`、`db_metadata.py`、`gateway_*.py`;
- **行业 MCP**:`extensions/mcp_servers/query_database_mcp/**`、`ai_chain_information_mcp/**`;
- **行业专属前端与种子**:产业链 Canvas、资讯 / 企业画像渲染器、行业 API 客户端、图标资源,
@@ -81,13 +81,13 @@ CE 前端不按工具名猜测远程 MCP 的业务结构。用户接入的远程
| `.hugagent-edition` | 仅在派生后出现的机器可读 `ce` 标识;让发布工具区分派生 CE checkout 与生成器异常缺失的源代码 checkout |
| `.github/workflows/desktop-release.yml` | 公开 CE 桌面发版 workflow,包含 release tag / 版本前置门禁 |
| `src/backend/api/routes/v1/__init__.py` | CE 路由注册表;`EE_ROUTERS` 恒为空 |
-| `src/backend/core/auth/permissions_iface.py` | 单租户 owner-only 权限接口;不导出团队权限函数 |
-| `src/backend/core/services/artifact_edition.py` | 个人空间 artifact 作用域接口;不暴露团队字段、权限或仓储方法 |
+| `src/backend/core/auth/personal_permissions_iface.py` | 单租户 owner-only 权限接口;不导出团队权限函数 |
+| `src/backend/core/files/edition.py` | 个人空间 artifact 作用域接口;不暴露团队字段、权限或仓储方法 |
| `src/backend/core/llm/tools/edition_{myspace,myspace_vfs,artifact_recovery}.py` | 个人空间工具、VFS 与恢复接口;组织空间实现不进入 CE |
-| `src/backend/core/config/edition_display_names.py` | CE 工具展示名;不包含团队工具名称 |
+| `src/backend/core/config/display_names.py` | CE 工具展示名;不包含团队工具名称 |
| `src/backend/core/memory/audit.py` | 记忆审计 no-op stub(同名接口、不落数据) |
| `src/backend/alembic/versions/ce_000{1,2}_*.py` | CE 独立迁移链基线与幂等 Schema 升级(见下节) |
-| `src/backend/core/services/edition_startup.py` | CE 启动接缝;为 EE 数据源恢复、蒸馏调度提供 no-op 实现,避免导入已裁除模块 |
+| `src/backend/orchestration/workers/edition_startup.py` | CE 启动接缝;为 EE 数据源恢复、蒸馏调度提供 no-op 实现,避免导入已裁除模块 |
| `src/backend/tests/ce_release/` | 可执行 CE 发布回归套件,覆盖登录、私有技能/MCP、数据库升级及启动/Compose 契约 |
| `src/backend/api/routes/v1/{agents,content,kb_models,projects}.py` | 去除管理端点与组织字段后的 CE API 契约 |
| `src/backend/extensions/mcp_servers/_ports.py` | 7 个通用工具的端口表(EE 行业工具与已退役端口标注 reserved) |
@@ -183,7 +183,7 @@ EE 始终全量建表。维护规则:**新增 EE 专属模型必须放进 `edi
| overlay 目录 | `ce/overlay/` |
| 派生树版本标识 | `ce/overlay/.hugagent-edition` |
| 公开桌面发版 workflow | `ce/overlay/.github/workflows/desktop-release.yml` |
-| CE/EE 建表边界 | `src/backend/core/db/edition_tables.py` |
+| CE/EE 建表边界 | `src/backend/edition_ee/db/edition_tables.py` |
| 幂等 Schema 升级器 | `src/backend/core/db/schema_reconcile.py` |
| 启动建表 CE 分支 | `src/backend/core/db/engine.py::init_db` |
| CE 独立迁移链 | `ce/overlay/src/backend/alembic/versions/ce_000{1,2}_*.py` |
diff --git a/document/zh-CN/getting-started/introduction.md b/document/zh-CN/getting-started/introduction.md
index c937aa53..8193115f 100644
--- a/document/zh-CN/getting-started/introduction.md
+++ b/document/zh-CN/getting-started/introduction.md
@@ -1,93 +1,118 @@
# 产品简介
-> 最后更新:2026-07-02
+> 最后更新:2026-10-23 | [English](../../en/getting-started/introduction.md)
-HugAgentOS 是一个企业级 AI Agent 平台:以 ReAct 智能体为内核,把对话、工具调用(MCP)、技能、沙箱代码执行、长期记忆、知识库 RAG、自动化与批量处理整合在一套全容器化(Docker Compose)的系统里。后端基于 FastAPI + AgentScope 2.0,前端基于 React 19 + Vite + Zustand,所有能力通过统一的 catalog 注册表按需启停,开箱即是一个可私有化部署的完整产品,而非框架半成品。
+HugAgentOS 是一个开源、可自托管的 **Agent Harness**。模型负责思考,Harness 负责模型之外的一切:
+任务在哪里运行、断了怎么接上、工具和沙箱怎么用、什么操作需要人批准、经验怎么沉淀。
-平台分为**社区版(CE,开源)**与**商业版(EE)**两个形态:社区版面向个人与小团队,包含完整的对话、工具、技能、沙箱、三层记忆、自动化、批量、数据画布等能力;商业版在此之上叠加团队协作、SSO、审计合规、行业数据工具、持久沙箱、云存储与完整管理台等组织级能力。详细边界见 [版本对比](../editions/overview.md)。
+它的核心是 **Durable Agent**:你提出的问题在云端执行,而不是依赖你的浏览器或电脑。关掉网页、
+合上笔记本,任务继续推进;服务器重启或宕机,其他实例从上次保存的进度接着做;不确定是否已经
+执行的外部操作不会被自动重做。在此之上,HugAgentOS 提供一个 7×24 小时在线的
+**Personal Agent**,并以 **HugAgentOS Cloud** 与 **HugAgentOS Local** 两种形态运行。
+
+平台分为**社区版(CE,开源)**与**商业版(EE)**:社区版让个人完整运行 Harness 与
+Personal Agent;商业版补充团队协作、SSO、审计合规、行业数据工具等组织级能力。
+详细边界见[版本对比](../editions/overview.md)。
+
+## 运行形态
+
+| 形态 | 运行位置 | 数据与状态 | 适合 | 部署文档 |
+|---|---|---|---|---|
+| **HugAgentOS Cloud** | 你的服务器或私有云 | PostgreSQL、Redis、S3 兼容存储、OpenSandbox | 7×24 Personal Agent、长任务、IM 渠道、多设备访问 | [Docker Compose](../deployment/docker-compose.md) |
+| **HugAgentOS Local** | 你的电脑(Windows / macOS / Linux) | 本地 SQLite、Milvus Lite、本地文件 | 离线、敏感数据、直接处理本地项目 | [桌面离线本机模式](../deployment/desktop-local-mode.md) |
+| **本机 + 云端** | 云端为控制面,本机为执行面 | 云端项目存云端,本地项目留本机 | 云端常驻,同时操作本机电脑与项目 | [桌面离线本机模式](../deployment/desktop-local-mode.md) |
+| 一键安装 | 开发机 | SQLite、进程内状态、子进程沙箱 | 试用与二次开发 | [无 Docker 一键安装](../deployment/quick-install.md) |
+
+7×24 持久运行需要 HugAgentOS Cloud。一键安装不提供跨进程的租约接管,只适合试用。
## 核心特性总览
| 能力 | 说明 | 版本 |
|---|---|---|
-| 对话智能体 | SSE 流式对话、AgentScope 2.0 ReActAgent、计划模式(Plan Mode)子智能体、深度思考、`[ref:tool-N]` 引用溯源、会话标题自动摘要 | CE |
-| 子智能体 | 用户自建子智能体、@提及协作、路由策略(`ROUTER_STRATEGY`) | CE(版本管理/组织级智能体库为 EE) |
-| MCP 工具生态 | 9 个内置 MCP server(CE 7 个通用工具:联网搜索、网页抓取、图表生成、批量计划、自动化任务管理、技能管理、知识检索;EE 2 个行业工具:数仓查询、产业链信息),独立 `mcp` 容器以 streamable-http 提供;用户可自助接入远程 HTTP/SSE MCP | CE(产业链/企业画像/数仓等行业工具为 EE) |
-| 技能系统 | Agent Skills(SKILL.md + 脚本):内置技能包、管理台上传、技能市场浏览安装、技能蒸馏 | CE(技能审核/组织治理为 EE) |
-| 沙箱执行 | `bash` / `sandbox_put_artifact` / `sandbox_get_artifact` 工具,三种 provider 可切换:script_runner(轻量内置)、OpenSandbox(持久会话 + Jupyter 上下文 + 快照)、CubeSandbox(E2B 兼容 MicroVM) | CE 轻量沙箱;持久沙箱(会话保持/快照)为 EE |
-| 记忆系统 | mem0 三层记忆:L1 个人画像、L2 向量记忆(Milvus)、L3 知识图谱(Neo4j),跨会话注入与后台抽取 | CE(记忆审计为 EE) |
-| 知识库 RAG | 文档上传、分块、向量 + 关键词混合检索、私有知识库;EE 可追加公共库与 Dify 外部知识库 | CE(公共库与 Dify 对接为 EE) |
-| 自动化 | 定时任务 / Cron 调度 / Prompt 与计划自动化 / 失败重试(`orchestration/schedulers/`) | CE |
-| 数据画布 | 基于 Univer 的在线表格编辑 | CE 个人编辑;多人实时协同为 EE |
-| 项目空间 / 我的空间 | 个人项目(文件容量配额)、个人网盘(myspace)、收藏、会话分享 | CE(团队文件夹/权限矩阵为 EE) |
-| 管理台 | `/admin` 内容管理(技能、能力中心、版本说明)+ `/config` 系统配置台(提示词版本池、模型、用户/团队、计费、审计、安全) | CE 基础管理;完整管理台(用户/团队/计费/审计/安全)为 EE |
-
-## CE 与 EE
-
-一句话区分:**社区版让一个人把平台用到极致,商业版让一个组织规模化地用起来**——团队协作、SSO、RBAC、审计合规、行业数据工具、持久沙箱、云存储、white-label 属于商业版。完整功能矩阵与授权方式见 [版本对比](../editions/overview.md) 与 [License 机制](../editions/license.md)。
+| Durable Run | 每次请求是一条持久化的 Run,后台执行;断线续播、运行中追加指令;租约接管与启动对账;工具副作用记录意图与回执,结果未知不自动重放 | CE |
+| Personal Agent | 单一对话入口、并行后台任务、永久云电脑、定时任务与心跳、IM 渠道、操作审批、文件化记忆与 Dreaming、连接本机电脑 | CE(授权治理与跨设备会话管理为 EE) |
+| 自主循环 | 目标跨轮存活、做完为止;worker 干活、只读评审员验收,命令可判定的需求由 driver 机检;中断后可续跑 | CE |
+| 对话与计划模式 | SSE 流式、ReAct 工具编排、深度思考、计划模式、引用溯源、上下文压缩检查点、会话分叉 | CE |
+| 子智能体 | 内置 explorer / worker / reviewer,用户自建子智能体,自动路由或 `@` 提及协作 | CE(组织级智能体库为 EE) |
+| MCP 工具与插件 | 内置联网搜索、网页抓取、知识检索、图表、自动化、技能与插件管理等工具;MCP 市场、自建私有 MCP、插件市场、浏览器自动化插件 | CE(数仓查询与行业工具为 EE) |
+| Agent Skills | SKILL.md + 脚本,内置技能、技能市场与个人技能 | CE(技能审核与组织治理为 EE) |
+| 沙箱执行 | OpenSandbox 会话、环境复用与快照恢复,或本地子进程沙箱;工作流后台作业以受管进程运行,可按租约接管 | CE(CubeSandbox 与团队挂载为 EE) |
+| 记忆与个人进化 | L1 画像、L2 做法记忆(Milvus,可选)、L3 关系图谱(Neo4j,可选);持久 Outbox 写入;进化建议逐条审批 | CE(记忆审计为 EE) |
+| 知识库 | 文档分块、向量 + 关键词混合检索、私有知识库、知识库 Wiki | CE(公共库管理与 Dify 对接为 EE) |
+| 领域本体治理 | Domain Pack 导入与版本发布、工具执行前规则检查、高风险答案独立评审、演进草案人工审核 | CE |
+| 自动化 | 自然语言创建定时任务、Cron 调度、失败重试 | CE |
+| 站点与应用托管 | 对话建站并一键发布;应用独立数据库与 MCP 托管 | CE(项目转换后的站点协作为 EE) |
+| 数据画布与产物 | Univer 在线表格、图表、报告、Office 文件、网页等产物与产物中心 | CE 个人编辑;多人协同为 EE |
+| 项目空间 / 我的空间 | 个人项目、文件夹、收藏、会话分享;桌面端可绑定本地项目目录 | CE(团队文件夹与权限矩阵为 EE) |
+| 桌面客户端 | Tauri 2 客户端,自带私有运行时,支持仅本机、仅云端、本机 + 云端三种模式 | CE |
## 技术栈
-| 层 | 技术 | 备注 |
-|---|---|---|
-| 后端 | FastAPI + Uvicorn(Python 3.11) | `src/backend/api/app.py`,统一响应 envelope |
-| 智能体框架 | AgentScope 2.0(`agentscope==2.0.0`) | ReActAgent + 工具注册,`core/llm/factory/build.py` |
-| 前端 | React 19 + Vite 7 + Zustand 5 + Ant Design 6 | `src/frontend/`,nginx 容器托管并反代 `/api` |
-| 数据库 | PostgreSQL 15(生产)/ SQLite(本地调试兜底) | SQLAlchemy 2 + Alembic 迁移 |
-| 缓存 / 会话 | Redis 7 | 会话存储、流式 follower(Redis Streams) |
-| 向量库 | Milvus 2.4(mem0 profile) | L2 向量记忆与自建知识库检索 |
-| 图数据库 | Neo4j 5 Community(mem0 profile,可选) | L3 知识图谱记忆 |
-| 沙箱 | script_runner sidecar / 阿里 OpenSandbox / 腾讯 CubeSandbox | `core/sandbox/` provider 协议,env 切换 |
-| 部署 | Docker Compose(profiles:`script_runner` / `opensandbox` / `mem0`) | 全部服务容器化,无本地 dev server |
+| 层 | 技术 |
+|---|---|
+| 智能体运行时 | AgentScope 2.0(ReActAgent)、Model Context Protocol |
+| 后端 | Python 3.11、FastAPI、SQLAlchemy 2、Alembic |
+| 前端 | React 19、TypeScript、Vite、Zustand、Ant Design |
+| 桌面端 | Tauri 2、私有 CPython 3.11 运行时 |
+| 数据与状态 | PostgreSQL 15 或 SQLite;Redis 7 或进程内状态 |
+| 存储 | S3 兼容存储(云端)、本地文件(桌面与一键安装) |
+| 沙箱 | OpenSandbox、本地子进程(CubeSandbox 为 EE) |
+| 可选记忆 | Milvus 2.4、Neo4j 5 Community、mem0 |
+| 部署 | Docker Compose、桌面安装包、一键安装脚本、Nginx |
## 整体架构
```
- ┌──────────────────────────────────────────────┐
- Browser ──► Nginx ────►│ FastAPI backend (src/backend/api/app.py) │
- (frontend 容器, │ api/routes/v1/* · 50+ 路由 · 统一 envelope │
- /api 反向代理) └───────────────────┬──────────────────────────┘
- │
- ┌──────────────────────┼─────────────────────────┐
- ▼ ▼ ▼
- orchestration/workflow.py core/services/* core/auth/*
- (SSE 流式编排:text / (业务服务层) (local / mock /
- tool_call / tool_result remote+SSO)
- / meta / done)
- │
- ┌─────────────┼───────────────────┬─────────────────────┐
- ▼ ▼ ▼ ▼
- core/llm/ orchestration/ orchestration/ core/memory/ (svc)
- factory/build strategy.py citations.py + memory_integration
- (AgentScope (路由策略) (引用解析) (mem0 检索/保存)
- 2.0 ReAct) │
- │ ▼
- ├──► core/llm/mcp_manager ──► mcp 容器 Milvus / Neo4j
- │ (9 个 MCP server,独立 streamable-http 端口) (mem0 profile)
- ├──► core/sandbox/* ──► script-runner / OpenSandbox / CubeSandbox
- └──► PostgreSQL · Redis · 存储 (local / S3 / OSS)
+ 入口:Web · 桌面客户端 · IM 渠道 · API · 定时任务 · 心跳
+ │
+ ▼
+ FastAPI(api/) 鉴权 · 版本路由 · 消息先落库再返回
+ │
+ ▼
+ Run 层(core/runs · core/chat/runtime)
+ 持久化 Run · 数据库租约 · 启动对账 · 事件流续播 · steer · 工具副作用回执
+ │
+ ▼
+ 智能体执行(core/agent_runtime · core/llm)
+ ReActAgent · 上下文按步装配与压缩 · 记忆 / 本体 / 进化集成
+ 外层编排(orchestration/):计划模式 · 自主循环 · 自动化
+ │
+ ┌────────────────┼──────────────────┐
+ ▼ ▼ ▼
+ MCP · 技能 · 插件 沙箱 · 后台作业 知识库 · 记忆
+ │ │ │
+ └────────────────┴──────────────────┘
+ ▼
+ PostgreSQL / SQLite · Redis · S3 / 本地存储 · Milvus · Neo4j
```
-请求主链路:浏览器 → frontend 容器内 nginx(`/api` 反代)→ FastAPI → `orchestration/workflow.py` 编排流式输出 → `core/llm/factory/build.py` 构建 ReActAgent → MCP 工具 / 沙箱 / 记忆 → SSE 事件(`text` / `tool_call` / `tool_result` / `meta` / `done`)回推前端。
+Personal Agent(`personal_agent/`)复用同一套 Run、租约与沙箱机制,增加身份、任务队列、
+调度、渠道、设备连接和工作区文件。更完整的请求生命周期见[架构总览](../architecture/overview.md)。
## 下一步
-- [10 分钟快速开始](quick-start.md)
-- [Docker Compose 完整部署](../deployment/docker-compose.md)
-- [环境变量参考](../deployment/environment-variables.md)
-- [架构总览](../architecture/overview.md)
+- [快速开始](quick-start.md)
+- [Personal Agent](../modules/personal-agent.md)
+- [对话与智能体编排](../modules/chat.md)
+- [自主循环](../modules/autonomous-loop.md)
+- [部署指南](../deployment/README.md)
## 相关源码
-| 功能 | 文件 |
+| 功能 | 路径 |
|---|---|
-| FastAPI 应用与路由注册 | `src/backend/api/app.py`、`src/backend/api/routes/v1/` |
-| 流式编排(SSE) | `src/backend/orchestration/workflow.py`、`orchestration/streaming.py` |
+| FastAPI 应用与路由 | `src/backend/api/app.py`、`src/backend/api/routes/v1/` |
+| Run 生命周期、租约与恢复 | `src/backend/core/runs/` |
+| 对话 worker、续播与转向 | `src/backend/core/chat/runtime/` |
+| 智能体执行入口 | `src/backend/core/agent_runtime/`(`runner.astream_chat_workflow`) |
| 智能体构建 | `src/backend/core/llm/factory/build.py` |
-| MCP server 与端口映射 | `src/backend/extensions/mcp_servers/`、`src/backend/extensions/mcp_servers/_ports.py`、`src/backend/core/config/mcp_config.py` |
-| 能力注册表 | `src/backend/core/config/catalog.json`、`core/config/catalog.py` |
-| 沙箱 provider | `src/backend/core/sandbox/`、`src/backend/core/config/settings.py::SandboxSettings` |
-| 记忆系统 | `src/backend/core/memory/`(service.py / pipeline.py)、`src/backend/orchestration/memory_integration.py` |
-| 自动化调度 | `src/backend/orchestration/schedulers/automation_scheduler.py` |
-| 前端入口(App / Admin / Config) | `src/frontend/src/main.tsx`、`App.tsx`、`AdminApp.tsx`、`ConfigApp.tsx` |
-| 版本与 License 设置 | `src/backend/core/config/settings.py::EditionSettings / LicenseSettings` |
+| Harness 契约与执行台账 | `src/backend/core/harness/` |
+| 工作流后台作业 | `src/backend/core/jobs/` |
+| 计划模式、自主循环、自动化 | `src/backend/orchestration/plans/`、`orchestration/loops/`、`orchestration/automations/` |
+| Personal Agent | `src/backend/personal_agent/` |
+| 记忆与进化 | `src/backend/core/memory/`、`src/backend/core/evolution/` |
+| 领域本体 | `src/backend/core/ontology/` |
+| 沙箱 | `src/backend/core/sandbox/` |
+| 内置 MCP、技能、插件与智能体 | `src/backend/extensions/` |
+| 前端入口 | `src/frontend/src/main.tsx`、`App.tsx` |
+| 桌面客户端 | `desktop/src-tauri/` |
diff --git a/document/zh-CN/getting-started/quick-start.md b/document/zh-CN/getting-started/quick-start.md
index 840f1976..c4053d7b 100644
--- a/document/zh-CN/getting-started/quick-start.md
+++ b/document/zh-CN/getting-started/quick-start.md
@@ -1,21 +1,85 @@
# 快速开始
-> 最后更新:2026-07-21 |
+> 最后更新:2026-10-23 |
> [English](../../en/getting-started/quick-start.md)
-个人试用可以选择一键安装,长期服务或服务隔离场景可以选择 Docker Compose。
-两种方式都需要准备 OpenAI 兼容 API 或本地模型。
+HugAgentOS 有三种上手方式。需要智能体 7×24 小时在云端持续运行时选择 HugAgentOS Cloud;
+想在自己的电脑上离线使用时选择 HugAgentOS Local 桌面客户端;做二次开发或快速试用时选择
+一键安装。所有方式都需要准备 OpenAI 兼容 API 或本地模型。
## 选择部署方式
-两种部署方式使用不同的运行与持久化模型。
-
| 方式 | 适用场景 | 运行形态 |
|---|---|---|
-| 一键安装 | 个人试用与开发 | SQLite、进程内状态、本地子进程沙箱 |
-| Docker Compose | 长期运行服务器与服务隔离 | PostgreSQL、Redis、容器沙箱、持久化服务卷 |
+| HugAgentOS Cloud(Docker Compose) | 7×24 运行、Personal Agent、IM 渠道、多设备访问 | PostgreSQL、Redis、OpenSandbox、持久化卷 |
+| HugAgentOS Local(桌面客户端) | 离线使用、敏感数据、处理本地项目 | 自带私有运行时、本地 SQLite 与文件 |
+| 一键安装 | 试用与二次开发 | SQLite、进程内状态、本地子进程沙箱 |
+
+**初始账号与密码均为 `admin`,首次登录必须修改;社区版不提供自助注册。**
+
+## 方式一:HugAgentOS Cloud(Docker Compose)
+
+安装 Git、Docker Engine 或 Docker Desktop 和 Docker Compose v2,然后运行:
+
+```bash
+git clone https://github.com/ZJU-REAL/HugAgentOS.git
+cd HugAgentOS
+cp .env.example .env
+mkdir -p data/storage
+docker compose build backend
+docker compose up -d --build
+```
+
+打开 [http://localhost:3002](http://localhost:3002),使用 `admin` / `admin`
+登录并修改密码,然后进入「设置 → 系统管理 → 模型服务」接入模型。使用
+`docker compose ps` 检查服务,使用 `docker compose down` 停止服务且保留数据。
+
+Profiles、数据持久化、生产配置和重建流程见
+[Docker Compose 部署指南](../deployment/docker-compose.md)。
+
+### 启用 Personal Agent(可选)
+
+Personal Agent 默认关闭。它使用独立的 OpenSandbox 控制器为每个用户维护一台永久云电脑。
+
+1. 构建专用电脑镜像:
+
+ ```bash
+ docker build -f docker/personal-agent/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .
+ ```
+
+2. 在 `.env` 中设置:
+
+ | 变量 | 值 |
+ |---|---|
+ | `PERSONAL_AGENT_ENABLED` | `true` |
+ | `HOST_STORAGE_PATH` | 宿主机上的绝对路径 |
+ | `PERSONAL_AGENT_OPENSANDBOX_API_KEY` | 至少 32 个字符的独立随机密钥,例如 `openssl rand -hex 32` 的输出 |
-## 方式一:一键安装
+3. 使用附加 Compose 文件启动:
+
+ ```bash
+ docker compose -f docker-compose.yml -f docker/personal-agent/compose.personal-agent.yml --profile personal-agent up -d
+ ```
+
+启动后,在「新建对话」下方点击「Personal Agent」即可使用。数据库迁移、镜像升级和渠道绑定见
+[Personal Agent](../modules/personal-agent.md)。密钥不要提交到 Git。
+
+## 方式二:HugAgentOS Local(桌面客户端)
+
+1. 从 [Releases](https://github.com/ZJU-REAL/HugAgentOS/releases) 下载对应平台
+ (Windows x86_64、macOS Apple Silicon / Intel、Linux x86_64)的桌面安装包。
+2. 首次启动时选择运行模式:
+ - **仅本机**:服务、数据和执行都在这台电脑上,可完全离线运行;
+ - **仅云端**:连接已部署的 HugAgentOS Cloud;
+ - **本机 + 云端**:使用云端账号,云端项目存云端,本地项目在本机执行;
+ Personal Agent 可以借此连接这台电脑。
+3. 选择本机模式时,客户端会校验并解压自带的运行时,通常数十秒内完成,不需要安装 Python、
+ Docker 或数据库。
+
+安装、升级回滚和数据目录见[桌面离线本机模式](../deployment/desktop-local-mode.md);
+绑定本地项目后的执行目录规则见[本机项目工作目录](../deployment/local-project-workspace.md)。
+
+## 方式三:一键安装
在 Linux、macOS 或通过 WSL2 使用 Windows 时选择此方式。开始前安装下列工具。
@@ -52,7 +116,7 @@ curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh
以后需要启动应用时,运行已安装的命令:
```bash
-~/.hugagent/venv/bin/hugagent
+~/.hugagent/venv/bin/hugagent-server
```
也可以把命令加入 Shell 的 `PATH`:
@@ -61,33 +125,17 @@ curl -fsSL https://raw.githubusercontent.com/ZJU-REAL/HugAgentOS/main/install.sh
export PATH="$HOME/.hugagent/venv/bin:$PATH"
```
-## 方式二:Docker Compose
-
-需要持久化服务卷和容器隔离时选择此方式。安装 Git、Docker Engine 或
-Docker Desktop 和 Docker Compose v2,然后运行:
-
-```bash
-git clone https://github.com/ZJU-REAL/HugAgentOS.git
-cd HugAgentOS
-cp .env.example .env
-mkdir -p data/storage
-docker compose up -d --build
-```
-
-打开 [http://localhost:3002](http://localhost:3002),使用 `admin` / `admin`
-登录并修改密码,然后进入「设置 → 系统管理 → 模型服务」接入模型。使用
-`docker compose ps` 检查服务,使用 `docker compose down` 停止服务且保留数据。
-
-Profiles、数据持久化、生产配置和重建流程见
-[Docker Compose 部署指南](../deployment/docker-compose.md)。
+一键安装不提供跨进程的租约接管,不适合 7×24 运行。
## 下一步
首次登录成功后,可以继续阅读:
-- [完整无 Docker 安装指南](../deployment/quick-install.md):安装选项、能力边界和故障排查;
+- [产品简介](introduction.md):Durable Agent、运行形态与能力总览;
- [模型接入](../modules/model-providers.md);
-- [领域本体快速入门](domain-ontology-quickstart.md):用最小 Domain Pack 构建受治理工作流;
+- [Personal Agent](../modules/personal-agent.md)与[自主循环](../modules/autonomous-loop.md);
- [MCP 工具](../modules/mcp-tools.md)、
[Agent Skills](../modules/agent-skills.md)和
- [私有知识库](../modules/knowledge-base.md)。
+ [私有知识库](../modules/knowledge-base.md);
+- [领域本体快速入门](domain-ontology-quickstart.md):用最小 Domain Pack 构建受治理工作流;
+- [完整无 Docker 安装指南](../deployment/quick-install.md):安装选项、能力边界和故障排查。
diff --git a/document/zh-CN/modules/admin-console.md b/document/zh-CN/modules/admin-console.md
index 122ad6e4..a90ed117 100644
--- a/document/zh-CN/modules/admin-console.md
+++ b/document/zh-CN/modules/admin-console.md
@@ -53,11 +53,11 @@ HugAgentOS 提供**两个相互独立的管理入口**,分别面向内容运
`/v1/admin/skills`(`api/routes/v1/admin_skills.py`,`ADMIN_TOKEN`):技能全生命周期管理——CRUD、zip 上传、启停、排序、图标、依赖扫描与编辑、技能内单文件读写删、复刻内置技能、导入 / 导出。技能存于 `admin_skills` 表,技能体系详见 [Agent 技能](agent-skills.md)。
-`/v1/admin/skill-drafts`(`api/routes/v1/admin_skill_drafts.py`,`ADMIN_TOKEN`):自动蒸馏出的技能草稿审核——列表 / 计数 / 详情、先编辑后通过(approve)、驳回(reject)、删除,以及手动触发每日蒸馏扫描。
+`/v1/admin/skill-drafts`(`features/distillation/api/admin_skill_drafts.py`,`ADMIN_TOKEN`):自动蒸馏出的技能草稿审核——列表 / 计数 / 详情、先编辑后通过(approve)、驳回(reject)、删除,以及手动触发每日蒸馏扫描。
### 市场审核(admin_marketplace)(商业版 EE:content_admin)
-`/v1/admin/marketplace`(`api/routes/v1/admin_marketplace.py`,`ADMIN_TOKEN`):两件事——
+`/v1/admin/marketplace`(`features/marketplace/api/admin_marketplace.py`,`ADMIN_TOKEN`):两件事——
1. **安装市场技能为全局技能**:浏览市场列表(标注「是否已全局安装」)、安装后 owner 为空、全员可用,可继续在「技能管理」编辑。
2. **审核用户上架申请**:申请列表(按 status 过滤)、详情(含 SKILL.md 预览)、通过(上架,全员可装)、驳回 / 下架。
@@ -91,14 +91,14 @@ GitHub 绑定默认在此面板维护,也可用 `FEEDBACK_GITHUB_*` 环境变
### 知识库管理(admin_kb)(商业版 EE:content_admin)
-`/v1/admin/kb`(`api/routes/v1/admin_kb.py`,`ADMIN_TOKEN`):自建**公共知识库**管理(`KBSpace.visibility == "public"`,归属系统属主),能力镜像用户态 `kb.py`——库 CRUD、AI 生成简介、文档上传 / 列表 / 原文预览(Office 转 PDF)/ 删除 / 重新索引、分块预览与逐块编辑(内容 / 标签 / 问题)。公共库对全部用户在能力目录可见、可被检索。Dify 模式下仅只读展示 Dify 数据集,写操作返回 409。详见 [知识库](knowledge-base.md)。
+`/v1/admin/kb`(`edition_ee/routes/admin_kb.py`,`ADMIN_TOKEN`):自建**公共知识库**管理(`KBSpace.visibility == "public"`,归属系统属主),能力镜像用户态 `kb.py`——库 CRUD、AI 生成简介、文档上传 / 列表 / 原文预览(Office 转 PDF)/ 删除 / 重新索引、分块预览与逐块编辑(内容 / 标签 / 问题)。公共库对全部用户在能力目录可见、可被检索。Dify 模式下仅只读展示 Dify 数据集,写操作返回 409。详见 [知识库](knowledge-base.md)。
### 沙箱管理(admin_sandbox + config_security/sandbox)
两个互补面板:
-- `/v1/admin/sandbox`(`api/routes/v1/admin_sandbox.py`,`ADMIN_TOKEN`,**商业版 EE:content_admin**):聚合技能依赖清单预览、触发沙盒镜像重建 + 容器热切、重建历史与日志查询。
-- `/v1/config/security/sandbox/*`(`api/routes/v1/config_security.py`,`CONFIG_TOKEN`,**商业版 EE:system_config**):只读安全视图——沙盒概览、运行实例列表 / 详情、快照、重建历史、脱敏配置。Provider 不支持的能力(如 ScriptRunner 无法枚举实例)返回 `code=42210`,前端置灰。
+- `/v1/admin/sandbox`(`features/sandbox_ops/api/admin_sandbox.py`,`ADMIN_TOKEN`,**商业版 EE:content_admin**):聚合技能依赖清单预览、触发沙盒镜像重建 + 容器热切、重建历史与日志查询。
+- `/v1/config/security/sandbox/*`(`features/security_console/api/config_security.py`,`CONFIG_TOKEN`,**商业版 EE:system_config**):只读安全视图——沙盒概览、运行实例列表 / 详情、快照、重建历史、脱敏配置。Provider 不支持的能力(如 ScriptRunner 无法枚举实例)返回 `code=42210`,前端置灰。
详见 [沙箱](sandbox.md)。
@@ -166,13 +166,13 @@ GitHub 绑定默认在此面板维护,也可用 `FEEDBACK_GITHUB_*` 环境变
| 管理凭证依赖 | `src/backend/api/deps.py` |
| License 能力位 | `src/backend/edition_ee/licensing/features.py`、`src/backend/edition_ee/licensing/deps.py` |
| 技能 / 草稿 / 市场 | `src/backend/api/routes/v1/admin_skills.py`、`admin_skill_drafts.py`、`admin_marketplace.py` |
-| 问题反馈与 GitHub 发布 | `src/backend/edition_ee/routes/feedbacks.py`、`admin_feedbacks.py`、`services/feedback_service.py` |
+| 问题反馈与 GitHub 发布 | `src/backend/edition_ee/routes/feedbacks.py`、`admin_feedbacks.py`、`edition_ee/services/feedback_service.py` |
| 提示词 / MCP / 子智能体 | `src/backend/api/routes/v1/admin_prompts.py`、`admin_mcp_servers.py`、`admin_agents.py` |
-| 知识库 / 沙盒 | `src/backend/edition_ee/routes/admin_kb.py`、`src/backend/api/routes/v1/admin_sandbox.py` |
+| 知识库 / 沙盒 | `src/backend/edition_ee/routes/admin_kb.py`、`src/backend/features/sandbox_ops/api/admin_sandbox.py` |
| 计费 / 用量 / 日志 / 会话审查 | `src/backend/api/routes/v1/admin_billing.py`、`admin_usage_logs.py`、`admin_logs.py`、`admin_chat_history.py` |
| 内容管理 | `src/backend/api/routes/v1/content.py` |
| 用户 / 团队 / 邀请 / License(EE) | `src/backend/edition_ee/routes/config_users.py`、`config_teams.py`、`config_invites.py`、`config_license.py` |
-| 安全 | `src/backend/api/routes/v1/config_security.py` |
+| 安全 | `src/backend/features/security_console/api/config_security.py` |
| 服务配置 | `src/backend/api/routes/v1/service_configs.py` |
延伸阅读:[认证与权限](auth.md) · [提示词系统](prompts.md) · [版本与授权](../editions/overview.md)
diff --git a/document/zh-CN/modules/agent-skills.md b/document/zh-CN/modules/agent-skills.md
index a1927829..c3d857dd 100644
--- a/document/zh-CN/modules/agent-skills.md
+++ b/document/zh-CN/modules/agent-skills.md
@@ -29,11 +29,11 @@
└── _scripts.json # 可选:脚本白名单声明(缺省时按扩展名自动探测)
```
-解析与数据结构在 `core/agent_skills/registry.py`:`AgentSkillMetadata`(轻量元数据,列表场景用)与 `AgentSkillSpec`(含完整指令,执行场景用)。`_scripts.json` 缺失时 `loader._auto_detect_scripts` 按扩展名自动生成白名单,用户无需手写。
+解析与数据结构在 `core/capabilities/skills/registry.py`:`AgentSkillMetadata`(轻量元数据,列表场景用)与 `AgentSkillSpec`(含完整指令,执行场景用)。`_scripts.json` 缺失时 `loader._auto_detect_scripts` 按扩展名自动生成白名单,用户无需手写。
## 多源加载与优先级
-`core/agent_skills/loader.py::MultiSourceSkillLoader` 通过后端抽象层(`backends/`:filesystem / database / composite)从多个来源加载技能,同 ID 冲突时高优先级覆盖(`core/agent_skills/config.py`):
+`core/capabilities/skills/loader.py::MultiSourceSkillLoader` 通过后端抽象层(`backends/`:filesystem / database / composite)从多个来源加载技能,同 ID 冲突时高优先级覆盖(`core/capabilities/skills/config.py`):
| 来源 | 目录 | 优先级 | 说明 |
|---|---|---|---|
@@ -142,11 +142,11 @@
- **范围**:主对话链路 + 子智能体。子智能体绑定的插件同样延迟加载,但激活只在该次运行内生效、不落库(子智能体运行短暂且相互隔离,落到父会话的键上会把激活泄漏进主智能体装配);收窄模式(对话模式)圈定的插件保持全量装配(那是管理员的显式圈定);组件含 stdio 形态 MCP 的插件不延迟。
- **开关**:环境变量 `PLUGIN_PROGRESSIVE_LOADING=false` 可整体回退到全量装配。
-实现:`core/plugins/runtime/__init__.py`(延迟解析 / 目录渲染 / 激活工具)+ `core/llm/factory/build.py` 装配接入。
+实现:`core/capabilities/plugins/runtime/__init__.py`(延迟解析 / 目录渲染 / 激活工具)+ `core/llm/factory/build.py` 装配接入。
## 技能市场(Skill Marketplace)
-技能市场是"预置 + 社区"双来源的可安装技能库,核心服务在 `core/services/marketplace_service.py`。
+技能市场是"预置 + 社区"双来源的可安装技能库,核心服务在 `features/marketplace/`。
### 浏览与安装
@@ -197,7 +197,7 @@
GET /v1/admin/visibility/principals ← 用户/团队/角色简表(选择器数据源)
```
-存储:`marketplace_listing_states.visibility`(缺行 = public)+ `marketplace_visibility_grants` 白名单表;用户侧解析收口在 `core/auth/marketplace_visibility.py`(角色含个人直配与经团队获得的部门默认角色)。
+存储:`marketplace_listing_states.visibility`(缺行 = public)+ `marketplace_visibility_grants` 白名单表;用户侧解析收口在 `edition_ee/auth/marketplace_visibility.py`(角色含个人直配与经团队获得的部门默认角色)。
## 管理员技能管理
@@ -206,7 +206,7 @@
- **创建/编辑**:手写 SKILL.md(`POST /`、`PUT /{id}`),或 **zip 上传**整个技能包(`POST /upload`,自动识别目录前缀、二进制文件 base64 入库,上限 200MB);
- **文件级管理**:`GET/PUT/DELETE /{id}/files/{filename}` 在线编辑技能附属文件;
- **运维**:启停(`/{id}/toggle`)、排序、图标、fork 复制、批量导出/导入(跨环境迁移);
-- **依赖管理**:`POST /{id}/rescan-deps` 用 `core/agent_skills/deps_detector.py` 静态扫描脚本的 pip/apt 依赖,`PUT /{id}/dependencies` 人工修正;聚合后的依赖清单用于沙箱镜像重建(见[沙箱模块](sandbox.md)的管理员依赖重建)。
+- **依赖管理**:`POST /{id}/rescan-deps` 用 `core/capabilities/skills/deps/detector.py` 静态扫描脚本的 pip/apt 依赖,`PUT /{id}/dependencies` 人工修正;聚合后的依赖清单用于沙箱镜像重建(见[沙箱模块](sandbox.md)的管理员依赖重建)。
## 技能蒸馏(Distillation)(商业版 EE)
@@ -223,7 +223,7 @@
approve → 转正为 AdminSkill;reject / delete;可手动触发 trigger-daily-scan
```
-阈值、关键词、预算等全部可由 `DISTILL_*` 环境变量覆盖(`core/config/distillation.py`)。蒸馏管线(`skill_distiller.py`、调度器)属**商业版 EE**,社区版派生树剔除;技能草稿审核台随之仅在商业版可见。
+阈值、关键词、预算等全部可由 `DISTILL_*` 环境变量覆盖(`features/distillation/config.py`)。蒸馏管线(`skill_distiller.py`、调度器)属**商业版 EE**,社区版派生树剔除;技能草稿审核台随之仅在商业版可见。
## 用户自助技能(能力中心)
@@ -265,22 +265,22 @@
| 路径 | 说明 |
|---|---|
-| `src/backend/core/agent_skills/registry.py` | SKILL.md 解析、元数据/完整 Spec 数据结构 |
-| `src/backend/core/agent_skills/loader.py` | 多源加载、DB 技能物化、沙箱路径重定向 |
-| `src/backend/core/agent_skills/config.py` | 加载源优先级、统一沙箱技能目录、内置技能同步 |
-| `src/backend/core/agent_skills/selector.py` | 按用户意图 LLM 选技能(渐进式披露) |
-| `src/backend/core/agent_skills/deps_detector.py` | 脚本 pip/apt 依赖静态探测 |
-| `src/backend/core/agent_skills/backends/` | filesystem / database / composite 加载后端 |
+| `src/backend/core/capabilities/skills/registry.py` | SKILL.md 解析、元数据/完整 Spec 数据结构 |
+| `src/backend/core/capabilities/skills/loader.py` | 多源加载、DB 技能物化、沙箱路径重定向 |
+| `src/backend/core/capabilities/skills/config.py` | 加载源优先级、统一沙箱技能目录、内置技能同步 |
+| `src/backend/core/llm/skill_selection.py` | 按用户意图 LLM 选技能(渐进式披露) |
+| `src/backend/core/capabilities/skills/deps/detector.py` | 脚本 pip/apt 依赖静态探测 |
+| `src/backend/core/capabilities/skills/backends/` | filesystem / database / composite 加载后端 |
| `src/backend/core/llm/tools/skill_tool.py` | 受限 view_text_file + {baseDir} 替换 + Runtime Hint |
| `src/backend/extensions/skills/default/` | 12 个内置技能(含 10 个 OfficeCLI 系列) |
| `src/backend/extensions/skills/marketplace/` | 45 个可安装市场技能包 |
-| `src/backend/core/services/marketplace_service.py` | 市场列表/安装/凭据注入/上架审核 |
-| `src/backend/api/routes/v1/marketplace.py` | 用户侧市场 API(浏览/安装/提交/撤回) |
-| `src/backend/api/routes/v1/admin_marketplace.py` | 管理员市场 API(全局安装/审核上架) |
+| `src/backend/features/marketplace/` | 市场列表/安装/凭据注入/上架审核 |
+| `src/backend/features/marketplace/api/marketplace.py` | 用户侧市场 API(浏览/安装/提交/撤回) |
+| `src/backend/features/marketplace/api/admin_marketplace.py` | 管理员市场 API(全局安装/审核上架) |
| `src/backend/api/routes/v1/admin_skills.py` | 管理员技能 CRUD / zip / 依赖管理 |
-| `src/backend/api/routes/v1/admin_skill_drafts.py` | 蒸馏草稿审核(EE) |
-| `src/backend/core/llm/skill_distiller.py` | 技能蒸馏 LLM 管线(EE) |
-| `src/backend/orchestration/schedulers/distillation_cron_scheduler.py` | 蒸馏每日调度(EE) |
+| `src/backend/features/distillation/api/admin_skill_drafts.py` | 蒸馏草稿审核(EE) |
+| `src/backend/features/distillation/` | 技能蒸馏 LLM 管线(EE) |
+| `src/backend/orchestration/workers/distillation_cron_scheduler.py` | 蒸馏每日调度(EE) |
| `src/backend/api/routes/v1/me_capabilities.py` | 用户自助技能/私有 MCP API |
相关文档:[沙箱执行系统](sandbox.md) · [MCP 工具系统](mcp-tools.md) · [能力目录](catalog.md) · [管理台](admin-console.md) · [版本与许可](../editions/overview.md)
diff --git a/document/zh-CN/modules/application-hosting.md b/document/zh-CN/modules/application-hosting.md
index 20a707ee..a3891425 100644
--- a/document/zh-CN/modules/application-hosting.md
+++ b/document/zh-CN/modules/application-hosting.md
@@ -6,11 +6,11 @@
配置 `APPLICATION_DATABASE_URL` 指向独立数据库。不能指向平台 `DATABASE_URL` 的同一数据库。云部署未配置时,管理界面提示未启用;本地模式可使用 SQLite,但生产使用 PostgreSQL。首次使用先执行 `python -m core.services.application_hosting_setup` 初始化注册表。
-可选 Compose 覆盖文件 `docker-compose.application-hosting.yml` 包含独立 PostgreSQL、持久卷和一次性初始化任务。分别提供不同的 `APPLICATION_DB_PASSWORD`(初始化)与 `APPLICATION_OWNER_DB_PASSWORD`(运行账号),使用 URL 安全的密码或正确编码连接串;初始化任务使用与后端相同的 Dockerfile 构建目标,并只读挂载当前后端源码。部署时后端也必须使用本次源码构建的镜像,并按仓库部署流程重建前端,使 nginx 的 `/applications-mcp/` 转发配置生效。
+可选 Compose 覆盖文件 `docker/application-hosting/compose.application-hosting.yml` 包含独立 PostgreSQL、持久卷和一次性初始化任务。分别提供不同的 `APPLICATION_DB_PASSWORD`(初始化)与 `APPLICATION_OWNER_DB_PASSWORD`(运行账号),使用 URL 安全的密码或正确编码连接串;初始化任务使用与后端相同的 Dockerfile 构建目标,并只读挂载当前后端源码。部署时后端也必须使用本次源码构建的镜像,并按仓库部署流程重建前端,使 nginx 的 `/applications-mcp/` 转发配置生效。
```bash
-docker compose -f docker-compose.yml -f docker-compose.application-hosting.yml config --quiet
-docker compose -f docker-compose.yml -f docker-compose.application-hosting.yml up -d
+docker compose -f docker-compose.yml -f docker/application-hosting/compose.application-hosting.yml config --quiet
+docker compose -f docker-compose.yml -f docker/application-hosting/compose.application-hosting.yml up -d
```
实际启动、重建、迁移和部署仍遵守项目授权流程。存量迁移使用独立应用数据库。本地验证不代表远程环境已经升级。端到端验证使用独立数据库及平台的本地 mock 认证;该认证模式仅供本地测试。
@@ -47,27 +47,7 @@ PostgreSQL 每个应用使用独立 schema 与受限 NOLOGIN 角色;查询和
已实现结构化 SQL 表、约束与索引、原子批量写入、幂等重试、版本更新、受限查询、应用隔离、匿名表单和声明式 MCP 托管。请求有行大小、批量大小、分页和记录配额限制。CSV 流式导出全表。
-未实现任意用户代码 MCP 容器、OAuth 授权服务器、外键关系设计器、全量备份恢复控制台和完整用量计费。当前固定 Bearer 方案不可宣称已完成 OAuth。真实聊天端到端驱动位于 `src/backend/tests/application_hosting_e2e.py`,独立测试环境由同目录的 `application_hosting_e2e_setup.py` 准备。通过生产聊天 API 验证模型、工具、沙箱与发布,验收结果以实际运行记录为准。
-
-## 本地端到端复现
-
-使用已配置真实模型、产业来源和 OpenSandbox 的本地 Docker 环境;先构建当前前端。以下驱动使用生产 API 与真实工具,不替换文件工具或发布接口。测试服务仅绑定回环端口,使用独立 platform_e2e_verified / applications_e2e_verified 数据库和独立存储目录。
-
-```bash
-npm --prefix src/frontend run build
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_e2e_setup.py
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_e2e.py
-```
-
-在 `http://127.0.0.1:18533/site/e2e-form-1007/` 用测试信息提交一次,邮箱使用 `browser-e2e@example.test`。再运行:
-
-```bash
-PYTHONPATH=src/backend .venv/bin/python src/backend/tests/application_hosting_lifecycle_e2e.py
-```
-
-生命周期验证会重启上述独立测试服务,并轮换、回滚、撤销和重新发布测试 MCP 凭据。凭据只保存在 `/tmp/application-hosting-full-e2e-credentials.json`(0600),不放入网页或验收报告。已有批次不自动清库;完整聊天重跑应使用干净的独立数据库批次。该流程不升级主环境,也不执行远程部署。
-
-重跑完整聊天时,先停止并移除 `hugagent-application-e2e-backend` 和 `hugagent-application-e2e-mcp` 这两个测试容器,再以 `APPLICATION_E2E_BATCH=run2` 执行 setup。旧批次数据库保留,驱动仍使用相同的回环访问地址。不要删除主环境容器或数据库。
+未实现任意用户代码 MCP 容器、OAuth 授权服务器、外键关系设计器、全量备份恢复控制台和完整用量计费。当前固定 Bearer 方案不可宣称已完成 OAuth。
## 桌面端
@@ -128,9 +108,9 @@ project_synced=false;保留新草稿并报告同步失败,核对线上版本
管理表单和回滚发布遇到尚未发布的项目草稿时返回冲突,不覆盖草稿。项目发布会等待文件工具登记,发布后刷新挂载文件的新版本;只有这些同步步骤完成才报告 project_synced=true。
-## 外部检验修复与升级
+## 部署要求与数据安全
-应用数据仍为可选模块。云端部署必须配置不同的 APPLICATION_DB_PASSWORD 与 APPLICATION_OWNER_DB_PASSWORD,并同时使用 docker-compose.yml 与 docker-compose.application-hosting.yml。首次部署及已有卷升级都执行 application-database-init;该步骤创建恢复历史表,把专用库对象交给 application_owner,并为已有应用角色补齐授权。后端仅使用 NOSUPERUSER、NOCREATEDB、CREATEROLE 的 application_owner;application_admin 只用于专用数据库初始化。CREATEROLE 是创建和回收应用隔离角色所需权限,因此必须使用独立 PostgreSQL 实例,不能复用业务平台数据库。初始化失败时不启动后端,不手工跳过依赖检查。
+应用数据仍为可选模块。云端部署必须配置不同的 APPLICATION_DB_PASSWORD 与 APPLICATION_OWNER_DB_PASSWORD,并同时使用 docker-compose.yml 与 docker/application-hosting/compose.application-hosting.yml。首次部署及已有卷升级都执行 application-database-init;该步骤创建恢复历史表,把专用库对象交给 application_owner,并为已有应用角色补齐授权。后端仅使用 NOSUPERUSER、NOCREATEDB、CREATEROLE 的 application_owner;application_admin 只用于专用数据库初始化。CREATEROLE 是创建和回收应用隔离角色所需权限,因此必须使用独立 PostgreSQL 实例,不能复用业务平台数据库。初始化失败时不启动后端,不手工跳过依赖检查。
访客 API 的成功及错误响应均带 CORS。云端访客写入与密码尝试采用 Redis 原子共享计数;所有后端进程必须使用同一 Redis。Redis 未配置或不可用时返回 503。仅未配置 Redis 的本机单进程模式使用内存计数。
diff --git a/document/zh-CN/modules/auth.md b/document/zh-CN/modules/auth.md
index e62c68cd..b916e5a6 100644
--- a/document/zh-CN/modules/auth.md
+++ b/document/zh-CN/modules/auth.md
@@ -56,7 +56,7 @@ HugAgentOS 的认证与权限体系覆盖三条互相独立的链路:**终端
- 完成状态存放在当前管理员的 `users_shadow.metadata` 中,并带版本号。刷新或重新登录
后不会重复进入已完成的向导。
-一键安装器的终端 `hugagent onboard` 已经完成账号和模型配置,因此成功结束时会写入
+一键安装器的终端 `hugagent-server onboard` 已经完成账号和模型配置,因此成功结束时会写入
同一个完成标记,不会再重复显示网页向导。
### Mock SSO(开发联调)
@@ -111,7 +111,7 @@ SSO 提供方响应必须是 JSON 对象,且成功码必须为有限数值 0
### 接口层(CE/EE 拆分接缝)
-`core/auth/permissions_iface.py` 是权限符号的**唯一导入入口**:主干代码(deps / files / chats / projects / kb 等)一律从这里导入,不直接引用具体实现。EE 主仓中它纯转发三个真实现;CE 派生树用单租户 stub 整体替换本文件,三个真实现物理不存在:
+`core/auth/personal_permissions_iface.py` 是权限符号的**唯一导入入口**:主干代码(deps / files / chats / projects / kb 等)一律从这里导入,不直接引用具体实现。EE 主仓中它纯转发三个真实现;CE 派生树用单租户 stub 整体替换本文件,三个真实现物理不存在:
| 实现文件 | 职责 |
|---|---|
@@ -176,7 +176,7 @@ SSO 提供方响应必须是 JSON 对象,且成功码必须为有限数值 0
| `PATCH /v1/me/api-keys/{key_id}` | 启用 / 禁用 |
| `DELETE /v1/me/api-keys/{key_id}` | 撤销 |
-Key 形如 `sk-jx-...`,在**所有 AUTH_MODE 下**都可作为 Bearer 调用业务 API(外部程序化调用通常没有 Cookie)。校验逻辑(启用 / 未撤销 / 未过期 / 用户权限位仍开启)集中在 `core/services/api_key_service.py::resolve_api_key`。
+Key 形如 `sk-jx-...`,在**所有 AUTH_MODE 下**都可作为 Bearer 调用业务 API(外部程序化调用通常没有 Cookie)。校验逻辑(启用 / 未撤销 / 未过期 / 用户权限位仍开启)集中在 `core/auth/api_keys.py::resolve_api_key`。
禁用或撤销 API Key 会拒绝新请求,并取消该 Key 已接收且仍在等待、运行或暂停待处理的对话任务。取消会停止后台执行并结束 SSE 流;已完成的外部工具操作不会撤回。重新启用 Key 不会恢复已取消任务。调用记录进入“需要处理”等停止状态后,耗时固定在停止时间。
@@ -201,13 +201,13 @@ Key 形如 `sk-jx-...`,在**所有 AUTH_MODE 下**都可作为 Bearer 调用
| SSO 票据交换 | `src/backend/edition_ee/auth/sso.py`、`src/backend/edition_ee/routes/auth.py` |
| Mock SSO / 本地登录注册页 | `src/backend/api/routes/v1/mock_sso.py`、`src/backend/core/auth/mock_ticket_store.py` |
| 密码哈希 | `src/backend/core/auth/password.py` |
-| 权限接口层(CE/EE 接缝) | `src/backend/core/auth/permissions_iface.py` |
+| 权限接口层(CE/EE 接缝) | `src/backend/core/auth/personal_permissions_iface.py` |
| 团队角色 / 文件权限(EE) | `src/backend/edition_ee/auth/roles.py`、`src/backend/edition_ee/auth/team_permissions.py` |
| 项目 / 会话分享权限(EE) | `src/backend/edition_ee/auth/project_permissions.py`、`src/backend/edition_ee/auth/chat_share_permissions.py` |
| 管理凭证依赖 | `src/backend/api/deps.py` |
| 用户资料 / 偏好 | `src/backend/api/routes/v1/users.py` |
| 用户权限位管理 | `src/backend/edition_ee/routes/config_users.py` |
-| 个人 API-Key | `src/backend/api/routes/v1/api_keys.py`、`src/backend/core/services/api_key_service.py` |
+| 个人 API-Key | `src/backend/api/routes/v1/api_keys.py`、`src/backend/core/auth/api_keys.py` |
| 能力中心自助(owner 隔离) | `src/backend/api/routes/v1/me_capabilities.py` |
| 邀请码 | `src/backend/edition_ee/auth/invite.py`、`src/backend/edition_ee/routes/config_invites.py` |
| 团队管理 | `src/backend/edition_ee/routes/config_teams.py`、`src/backend/edition_ee/routes/me_teams.py` |
diff --git a/document/zh-CN/modules/automation.md b/document/zh-CN/modules/automation.md
index cd888e35..2c53f631 100644
--- a/document/zh-CN/modules/automation.md
+++ b/document/zh-CN/modules/automation.md
@@ -6,8 +6,8 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两
| 能力 | 一句话定位 | 后端入口 |
|---|---|---|
-| 定时自动化 | 按 cron 周期(或一次性 / 手动)自动执行一段 Prompt 或一个计划 | `api/routes/v1/automations.py` + `orchestration/schedulers/automation_scheduler.py` |
-| 计划模式 | AI 先把复杂任务拆成结构化步骤,确认后逐步执行 | `api/routes/v1/plans.py` + `orchestration/subagents/plan_mode.py` |
+| 定时自动化 | 按 cron 周期(或一次性 / 手动)自动执行一段 Prompt 或一个计划 | `features/automation/api/automations.py` + `orchestration/automations/automation_scheduler.py` |
+| 计划模式 | AI 先把复杂任务拆成结构化步骤,确认后逐步执行 | `api/routes/v1/plans.py` + `orchestration/plans/` |
## 定时自动化任务
@@ -17,7 +17,7 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两
### 任务模型
-任务存于 `scheduled_tasks` 表(`core/db/models/automation.py` 的 `ScheduledTask`),核心字段:
+任务存于 `scheduled_tasks` 表(`edition_ee/db/models/automation.py` 的 `ScheduledTask`),核心字段:
| 字段 | 说明 |
|---|---|
@@ -40,7 +40,7 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两
### 调度机制(automation_scheduler.py)
-`orchestration/schedulers/automation_scheduler.py` 是一个随后端启动的 asyncio 轮询调度器:
+`orchestration/automations/automation_scheduler.py` 是一个随后端启动的 asyncio 轮询调度器:
- **轮询**:每 15 秒(外加 0–5 秒随机抖动)查询 DB 中 `next_run_at` 到期的任务。
- **分布式锁**:触发前先抢 Redis 锁 `jx:auto:lock:{task_id}`,执行期间持续续期,完成后按持有者令牌释放,防止多实例重复触发。
@@ -48,6 +48,7 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两
- **执行时长**:单次执行没有固定总时长上限;定时触发与手动执行的 Prompt、Plan、Loop 任务均可持续运行,关联会话及其接续运行也不受普通会话的总时长硬限制。
- **失败治理**:连续失败计数 ≥ `max_failures`(默认 3)时任务自动置为 `disabled`。
- **失联恢复**:启动时及每次轮询检查 `running` 记录。取得该任务的执行锁后,继续跟随已关联的耐久会话并保存最终结果;只有未关联会话的孤儿记录才改为 `failed`,并按需推进过期调度、收尾一次性任务。任务运行多久不影响恢复判断。执行锁每 15 秒续期,60 秒租约用于识别执行者失联,不限制任务总时长。错过的一次性任务同样先取得执行锁。
+- **执行锁关闭**:设置停止信号并取消续期,最多等待 2 秒后始终尝试按 token 删除锁;清理失败也移除本地占位并记录错误。执行 Task 结束后续期自动停止。结果投递总等待上限为 60 秒,投递失败不覆盖已提交的成功,也不自动重试。运行记录保存内部所有权 token,完成更新同时校验运行中状态与 token;恢复执行先认领该记录。
每次执行通过统一的 ChatRun 会话执行器创建**真实会话**,并在开跑时立即关联执行记录。指令任务实时展示正文、思考和工具调用;计划任务展示步骤进度。点击运行中的会话或刷新页面,可以跟随和重播与普通会话相同的事件流。用户可在会话中停止执行,指令任务还可通过耐久指令队列追加要求,在下一个安全的模型执行边界生效。主动停止不计入连续失败次数;执行锁失效或调度器关闭会取消后台会话。最终消息、文件、引用和结果投递继续由统一执行器与调度收尾模块负责。会话标题直接使用任务名称,不添加自动化前缀。更新前启动的执行无法补回未记录的实时事件。
@@ -63,7 +64,7 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两
## 计划模式
-计划模式把复杂任务拆成结构化步骤再执行,分两阶段(`orchestration/subagents/plan_mode.py`):
+计划模式把复杂任务拆成结构化步骤再执行,分两阶段(`orchestration/plans/`):
1. **生成(Phase 1)**:`POST /v1/plans/generate`(SSE 流式)——AI 分析任务描述,产出含标题、描述、步骤列表的计划草稿;每步可声明 `expected_tools` / `expected_skills` / `expected_agents`(期望用到的工具 / 技能 / 子智能体)。
2. **执行(Phase 2)**:`POST /v1/plans/{plan_id}/execute`(SSE 流式)——逐步顺序执行,每步用独立的 agent;步骤状态、工具调用日志、AI 输出逐步落库。
@@ -98,14 +99,37 @@ POST /v1/automations
| 主题 | 路径 |
|---|---|
-| 自动化 REST API | `src/backend/api/routes/v1/automations.py` |
-| 调度器(轮询 / 锁 / 恢复) | `src/backend/orchestration/schedulers/automation_scheduler.py` |
-| 自动化服务(cron 计算 / 状态机) | `src/backend/core/services/automation_service.py` |
-| 任务 / 运行记录模型 | `src/backend/core/db/models/automation.py` |
+| 自动化 REST API | `src/backend/features/automation/api/automations.py` |
+| 调度器(轮询 / 锁 / 恢复) | `src/backend/orchestration/automations/automation_scheduler.py` |
+| 自动化服务(cron 计算 / 状态机) | `src/backend/features/automation/service.py` |
+| 任务 / 运行记录模型 | `src/backend/features/automation/models.py` |
| 计划 REST API | `src/backend/api/routes/v1/plans.py` |
-| 计划生成 / 执行编排 | `src/backend/orchestration/subagents/plan_mode.py`、`src/backend/core/services/plan_service.py` |
+| 计划生成 / 执行编排 | `src/backend/orchestration/plans/`、`src/backend/core/chat/plans.py` |
| 计划 / 步骤模型 | `src/backend/core/db/models/agent.py` |
-| 前端自动化 UI | `src/frontend/src/components/lab/AutomationPanel.tsx` 等、`src/frontend/src/components/automation/RunTimelinePanel.tsx` |
+| 前端自动化 UI | `src/frontend/src/components/automation/AutomationPanel.tsx` 等、`src/frontend/src/components/automation/RunTimelinePanel.tsx` |
| 前端状态 | `src/frontend/src/stores/automationStore.ts`、`automationChatStore.ts` |
延伸阅读:[对话系统](chat.md) · [MCP 工具](mcp-tools.md) · [数据画布与产物](canvas-artifacts.md)
+
+## 多进程运行与恢复
+
+定时和手动触发共用一个调度循环,每 3 秒扫描一次,最多同时观察 8 个执行。
+消费触发信号、推进排期和插入 pending 执行记录在一个数据库事务内完成;
+数据库条件更新保护并发接纳。忙碌任务的新手动触发保留至当前执行结束。
+
+pending 表示已接纳、等待执行。调度器停止或租约失效只转交观察权,
+不会取消已接纳的会话;后续实例继续观察同一个持久执行及其后继会话。
+用户在会话中主动停止仍会停止实际执行。子执行关联和恢复参数与接纳一起提交,
+不通过重跑已启动命令恢复结果。运行中的通知投递仍沿用原有投递语义,
+此变更不承诺外部渠道的恰好一次投递。
+
+升级数据库须应用 FULL 的 automationqueue01 或 CE 的 ce_0032;
+本机 SQLite 使用已安装 automation 模块的 schema 初始化完成约束升级。
+存在 pending 记录时拒绝降级,以免丢弃已接纳工作。先停止旧版本调度器、
+迁移后启动新版本,不混用两套调度协议。
+
+Plan/Loop 将完整启动参数与子任务接纳一同持久化。接纳后、启动前崩溃时,
+后继调度器只领取 pending 子任务;已开始的 running 子任务不会从头重放。
+升级前应排空旧版在途任务,旧版未保存的参数不会由兼容路径猜测补齐。
+
+任务投递统一存储为 delivery_targets。升级迁移将旧 channel_id/conversation_id 转换为目标列表并删除旧字段,保留已明确配置的目标和其他元数据;请求处理不再读取旧格式。
diff --git a/document/zh-CN/modules/autonomous-loop.md b/document/zh-CN/modules/autonomous-loop.md
index d1c5d60a..5ba8e397 100644
--- a/document/zh-CN/modules/autonomous-loop.md
+++ b/document/zh-CN/modules/autonomous-loop.md
@@ -71,7 +71,7 @@
## 相关代码
-- 驱动器 `orchestration/autonomous_loop.py`、规划器 `orchestration/loop_planner.py`、评审员 `orchestration/subagents/loop_reviewer.py`
-- ChatRun 接入 `orchestration/chat_run_executor.py`(`start_autonomous_loop_run` / 启动对账 `resume_running_loops`)
-- 服务 `core/services/loop_service.py`(账本镜像 / 启动参数 / steering 队列)、API `api/routes/v1/loops.py`
+- 驱动器 `orchestration/loops/driver.py`、规划器 `orchestration/loops/planner.py`、评审员 `orchestration/loops/reviewer.py`
+- ChatRun 接入 `core/chat/runtime/`(`start_autonomous_loop_run` / 启动对账 `resume_running_loops`)
+- 服务 `orchestration/loops/run.py`(账本镜像 / 启动参数 / steering 队列)、API `api/routes/v1/loops.py`
- 数据表 `agent_loops` / `loop_iterations`
diff --git a/document/zh-CN/modules/browser-automation.md b/document/zh-CN/modules/browser-automation.md
index 9e674501..b0856011 100644
--- a/document/zh-CN/modules/browser-automation.md
+++ b/document/zh-CN/modules/browser-automation.md
@@ -105,7 +105,7 @@ Docker 使用沙箱镜像内的 Python Playwright 与 Chromium;桌面完整包
启动时实际尝试 Chromium 沙箱。仅经过宿主验证的外层隔离容器在出现明确的内部沙箱错误时允许关闭 Chromium 内层沙箱后重试;本机环境不采用此降级。启动失败显示 chromium_sandbox_unavailable、chromium_runtime_missing 或 chromium_launch_failed,而不是仅显示异常类型。
-更新 OpenSandbox 时需显式从仓库根目录构建运行镜像:docker build -f docker/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .。普通 docker compose up --build 不会重建这一按需启动的镜像。核对 OPENSANDBOX_IMAGE 实际标签,并在更新后用 browser_open 创建新会话;只有 Chromium 初始化及真实浏览器验证通过后才报告资源就绪。其他运行提供方同样需重建各自运行镜像。
+更新 OpenSandbox 时需显式从仓库根目录构建运行镜像:docker build -f docker/sandbox/Dockerfile.opensandbox -t hugagent-opensandbox-custom:latest .。普通 docker compose up --build 不会重建这一按需启动的镜像。核对 OPENSANDBOX_IMAGE 实际标签,并在更新后用 browser_open 创建新会话;只有 Chromium 初始化及真实浏览器验证通过后才报告资源就绪。其他运行提供方同样需重建各自运行镜像。
Fake-IP DNS 返回198.18.0.0/15时仍禁止连接,错误提示 fake_ip_dns,并建议配置 BROWSER_DNS_RESOLVER_URL 为受信任的 HTTPS JSON DNS 服务。解析失败返回 dns_failed;不得为解决公网 DNS 问题打开私网访问。缺失元素和重复定位器分别返回 element_not_found、ambiguous_locator,未执行动作不再标为结果未知。
diff --git a/document/zh-CN/modules/canvas-artifacts.md b/document/zh-CN/modules/canvas-artifacts.md
index b29574e2..6a92fafd 100644
--- a/document/zh-CN/modules/canvas-artifacts.md
+++ b/document/zh-CN/modules/canvas-artifacts.md
@@ -8,7 +8,7 @@
## 数据画布(Canvas)
-数据画布是聊天界面右侧的滑出面板(`src/frontend/src/components/canvas/CanvasPanel.tsx`),点击聊天附件卡片(`components/chat/ArtifactCardList.tsx`)或「我的空间」中的文件(`components/myspace/MySpacePanel.tsx`)即可打开。面板按文件类型选择渲染器:
+数据画布是聊天界面右侧的滑出面板(`src/frontend/src/components/canvas/CanvasPanel.tsx`),点击聊天附件卡片(`components/chat/MessageArtifacts.tsx`)或「我的空间」中的文件(`components/myspace/MySpacePanel.tsx`)即可打开。面板按文件类型选择渲染器:
| 文件类型 | 渲染方式 | 可编辑 |
|---|---|---|
@@ -78,14 +78,14 @@ Web Worker 中执行,并按真实存在的单元格稀疏转换,不遍历工
### 产物的产生与落库
-对话流式过程中,工具产出的文件先进入会话**工作区**(`core/llm/workspace`),只有被「钉住」(pinned)的文件才会在回合结束时由 `core/services/artifact_service.py::persist_artifacts` 写入 `artifacts` 表——这道**严格工作区闸门**保证临时中间文件不会污染我的空间。
+对话流式过程中,工具产出的文件先进入会话**工作区**(`core/sandbox/workspace`),只有被「钉住」(pinned)的文件才会在回合结束时由 `core/files/artifacts/service.py::persist_artifacts` 写入 `artifacts` 表——这道**严格工作区闸门**保证临时中间文件不会污染我的空间。
-物理存储由 `core/artifacts/store.py` 管理,按 `STORAGE_TYPE` 双模式:
+物理存储由 `core/files/artifacts/store.py` 管理,按 `STORAGE_TYPE` 双模式:
- `local`(默认,社区版):文件写在 `${STORAGE_PATH:-result}/artifacts/`,维护本地 JSON 索引(`index.json`),FileResponse 直出。
-- `oss`(商业版 EE,`cloud_storage`):上传到阿里云 OSS,本地索引同时备份到 OSS,容器重启不丢。S3 后端见 `core/storage/`。
+- `oss`(商业版 EE,`cloud_storage`):上传到阿里云 OSS,本地索引同时备份到 OSS,容器重启不丢。S3 后端见 `core/files/storage/`。
-下载统一走 `GET /files/{file_id}`(`api/routes/files.py`),按 `core/auth/permissions_iface.py::resolve_artifact_access` 做 owner ∪ team 权限判定,Office 文件支持转 PDF 在线预览。
+下载统一走 `GET /files/{file_id}`(`api/routes/files.py`),按 `core/auth/personal_permissions_iface.py::resolve_artifact_access` 做 owner ∪ team 权限判定,Office 文件支持转 PDF 在线预览。
### REST 接口(`/v1/artifacts`,`api/routes/v1/artifacts.py`)
@@ -141,8 +141,8 @@ officecli-xlsx、officecli-pptx 三个基座技能(以及它们之上的表单
|---|---|
| 画布面板 / Univer 表格 | `src/frontend/src/components/canvas/CanvasPanel.tsx`、`UniverSpreadsheet.tsx`、`src/frontend/src/stores/canvasStore.ts` |
| Artifact REST API | `src/backend/api/routes/v1/artifacts.py` |
-| Artifact 存储(local/OSS) | `src/backend/core/artifacts/store.py` |
-| 产物落库(工作区闸门) | `src/backend/core/services/artifact_service.py` |
+| Artifact 存储(local/OSS) | `src/backend/core/files/artifacts/store.py` |
+| 产物落库(工作区闸门) | `src/backend/core/files/artifacts/service.py` |
| 文件下载 / 预览 | `src/backend/api/routes/files.py` |
| 会话分享 API | `src/backend/api/routes/v1/chat_shares.py` |
| 分享前端 | `src/frontend/src/SharePreviewApp.tsx`、`src/frontend/src/components/share/ShareRecordsPage.tsx` |
diff --git a/document/zh-CN/modules/catalog.md b/document/zh-CN/modules/catalog.md
index d8d1bd40..7e0529be 100644
--- a/document/zh-CN/modules/catalog.md
+++ b/document/zh-CN/modules/catalog.md
@@ -13,7 +13,7 @@ core/config/catalog.json 系统级单一真源(admin 启停 + 展
core/config/catalog.py 公共 API:get_catalog / is_enabled / get_enabled_ids
│ set_enabled / reorder_items
▼
-core/config/catalog_resolver.py 合并层:catalog.json 默认 ∩ 用户 DB 覆盖
+core/capabilities/catalog/resolver.py 合并层:catalog.json 默认 ∩ 用户 DB 覆盖
│ resolve_all_runtime_enabled(db, user_id) → (skills, agents, mcps)
│ resolve_explicit_runtime_capabilities(...) → 本轮显式调用(忽略个人开关,保留硬门控)
▼
@@ -39,7 +39,7 @@ core/config/catalog_resolver.py 合并层:catalog.json 默认 ∩ 用户 D
}
```
-`catalog_loader.py` 在加载时做**动态源同步**:从技能 loader(`core/agent_skills/`)与 MCP 配置服务(`core/services/mcp_service.py`,DB 表 `admin_mcp_servers`)发现新 id 自动补入、名称/描述/版本随源刷新、源中已消失的陈旧项自动移除;展示详情(能力中心介绍 markdown、图标)属运行时字段,不落盘。结果带短 TTL 内存缓存,写操作调 `invalidate_catalog_cache()` 失效。
+`catalog_loader.py` 在加载时做**动态源同步**:从技能 loader(`core/capabilities/skills/`)与 MCP 配置服务(`core/capabilities/mcp/service.py`,DB 表 `admin_mcp_servers`)发现新 id 自动补入、名称/描述/版本随源刷新、源中已消失的陈旧项自动移除;展示详情(能力中心介绍 markdown、图标)属运行时字段,不落盘。结果带短 TTL 内存缓存,写操作调 `invalidate_catalog_cache()` 失效。
### 门控 API
@@ -54,7 +54,7 @@ get_enabled_ids("mcp") # 某类全部启用 id
## 用户级覆盖(catalog_overrides)
-用户在能力中心里开/关某项能力,写入 DB 表 `catalog_overrides`(`core/services/catalog_service.py`),按用户独立、互不影响:
+用户在能力中心里开/关某项能力,写入 DB 表 `catalog_overrides`(`core/capabilities/catalog/overrides.py`),按用户独立、互不影响:
- **合并算法**(`catalog_resolver.py::_merge_kind`):覆盖只能翻转 base catalog 中**已存在**项的启用位,不能复活已删除项。
- **管理员锁**:base catalog 中 `enabled=false` 的项,用户覆盖**不可**重新启用,且在 `/v1/catalog` 响应中对前台完全隐藏。
@@ -66,7 +66,7 @@ get_enabled_ids("mcp") # 某类全部启用 id
- **显式插件使用引导**:选择插件后加载其技能和 MCP 工具,提示模型优先按需使用;不限制首个工具调用,也不要求必须调用才能完成回答。
- **会话级能力恢复**:后续回合在个人开关筛选前恢复已激活的技能、连接器及插件组件,使技能清单与工具名/前缀在该会话中稳定;恢复时仍重新校验安装、管理员全局状态、依赖与归属,硬门控变化会立即生效。
-每次对话时 `core/chat/context.py::resolve_enabled_capabilities()` 把默认合并结果写入 workflow context;`api/routes/v1/chats/__init__.py::_resolve_explicit_capability_invocation()` 再校验插件安装实例与能力归属,从服务端安装记录解析组件,并把通过硬门控的显式选择并入当前回合。`core/llm/session_capabilities.py` 和 `core/plugins/runtime/__init__.py::resolve_sticky_plugin_capabilities()` 在后续回合恢复会话已激活能力。
+每次对话时 `core/chat/context.py::resolve_enabled_capabilities()` 把默认合并结果写入 workflow context;`api/routes/v1/chats/__init__.py::_resolve_explicit_capability_invocation()` 再校验插件安装实例与能力归属,从服务端安装记录解析组件,并把通过硬门控的显式选择并入当前回合。`core/llm/runtime/session_capabilities.py` 和 `core/capabilities/plugins/runtime/__init__.py::resolve_sticky_plugin_capabilities()` 在后续回合恢复会话已激活能力。
## /v1/catalog 路由与 KB 注入
@@ -116,20 +116,19 @@ get_enabled_ids("mcp") # 某类全部启用 id
## 管理端能力管理
- **`/admin` 内容管理台**:技能上传/启停/排序(`api/routes/v1/admin_skills.py`,内部调 `catalog.set_enabled` / `reorder_items` 回写 catalog.json)、MCP server 管理(`admin_mcp_servers.py`,DB 表 + 试连 + 缓存刷新)、子智能体管理(`admin_agents.py`)、技能市场上架审核(`admin_marketplace.py`,**商业版 EE**)。
-- **catalog 快照迁移**:`scripts/export_content.py --only catalog` 导出 catalog.json + `catalog_overrides`,`scripts/import_content.py --catalog ` 导入。
## 相关源码
| 主题 | 路径 |
|---|---|
-| 公共 API(is_enabled 等) | `src/backend/core/config/catalog.py` |
+| 公共 API(is_enabled 等) | `src/backend/core/capabilities/catalog/` |
| 单一真源文件 | `src/backend/core/config/catalog.json`(`CATALOG_PATH` 可覆盖) |
-| 加载 / 动态同步 / 缓存 | `src/backend/core/config/catalog_loader.py`,`catalog_common.py`,`catalog_migration.py` |
-| 用户覆盖合并 | `src/backend/core/config/catalog_resolver.py`,`core/services/catalog_service.py` |
+| 加载 / 动态同步 / 缓存 | `src/backend/core/capabilities/catalog/loader.py`,`catalog_common.py`,`catalog_migration.py` |
+| 用户覆盖合并 | `src/backend/core/capabilities/catalog/resolver.py`,`core/capabilities/catalog/overrides.py` |
| 目录路由 | `src/backend/api/routes/v1/catalog.py` |
| 用户自助能力 | `src/backend/api/routes/v1/me_capabilities.py` |
-| MCP 服务配置(DB) | `src/backend/core/services/mcp_service.py`,`api/routes/v1/admin_mcp_servers.py` |
-| 技能管理 | `src/backend/api/routes/v1/admin_skills.py`,`core/agent_skills/` |
-| Dify KB 注入(EE) | `src/backend/edition_ee/kb/dify.py`,经共享接缝 `core/kb/external_provider.py` 调用 |
+| MCP 服务配置(DB) | `src/backend/core/capabilities/mcp/service.py`,`api/routes/v1/admin_mcp_servers.py` |
+| 技能管理 | `src/backend/api/routes/v1/admin_skills.py`,`core/capabilities/skills/` |
+| Dify KB 注入(EE) | `src/backend/edition_ee/kb/dify.py`,经共享接缝 `features/knowledge/extension.py` 调用 |
| 前端能力中心 | `src/frontend/src/components/catalog/`,`stores/catalogStore.ts` |
| 工厂消费侧 | `src/backend/core/llm/factory/build.py::_effective_mcp_server_keys` |
diff --git a/document/zh-CN/modules/chat.md b/document/zh-CN/modules/chat.md
index 0e5f7005..e6540f58 100644
--- a/document/zh-CN/modules/chat.md
+++ b/document/zh-CN/modules/chat.md
@@ -71,10 +71,10 @@ orchestration/chat_run_executor.py::start_run()
▼
orchestration/workflow.py::astream_chat_workflow()
│ ├─ orchestration/memory_integration.py 非阻塞记忆检索(后台 task + 预算超时)
- │ ├─ core/config/catalog_resolver.py 解析本次启用的 skills/mcp/kb
+ │ ├─ core/capabilities/catalog/resolver.py 解析本次启用的 skills/mcp/kb
│ ├─ core/llm/factory/build.py::create_agent_executor()
│ │ MCP 连接池 + 技能注册 + 文件工具 + 系统提示词 + 中间件 → Agent
- │ ├─ core/llm/context_manager.py 历史按 token 预算裁剪
+ │ ├─ core/llm/context/window_policy.py 历史按 token 预算裁剪
│ └─ orchestration/streaming.py::StreamingAgent.stream()
│ 消费 agent.reply_stream(),归并 25 种细粒度事件并保留工具参数增量
▼
@@ -85,7 +85,7 @@ SSE follower:chat_run_executor.follow_run_as_sse()
### Run 解耦与断线续播
-每次发送消息会创建一条 `ChatRun` 并启动后台任务(`orchestration/chat_run_executor.py`),事件写入 Redis Stream(`maxlen=5000`;默认 TTL 为最长人机等待 7200 秒加 1800 秒恢复余量)。HTTP 连接只是"跟随者",因此:
+每次发送消息会创建一条 `ChatRun` 并启动后台任务(`core/chat/runtime/`),事件写入 Redis Stream(`maxlen=5000`;默认 TTL 为最长人机等待 7200 秒加 1800 秒恢复余量)。HTTP 连接只是"跟随者",因此:
| 能力 | 端点 |
|---|---|
@@ -133,7 +133,7 @@ SSE follower:chat_run_executor.follow_run_as_sse()
| 替我批准 | `auto` | 普通写入 / 编辑直接放行;**删除类操作**(`DESTRUCTIVE_OPS`)与本地策略标了 `danger:<类别>` 的命令仍然停下来问 |
| 完全放开 | `full` | 所有工具确认一律不再询问 |
-档位在 `core/llm/tool_permissions.py` 里统一声明(`APPROVAL_ASK` / `APPROVAL_AUTO` / `APPROVAL_FULL`),随对话上下文传到 `PermissionRuntime`,是所有工具确认闸的唯一判据;任何一档都只跳过"问"这一步,本地安全策略判定的硬拒绝仍然拒绝。"这次危不危险"由 `core/sandbox/permissions/models.py::danger_categories()` 从裁决理由里的 `danger:` 前缀还原,不靠匹配提示文案。读到早期版本存下的 `standard` / `readonly` 时按最保守的 `ask` 兜底。
+档位在 `core/llm/tool_permissions/` 里统一声明(`APPROVAL_ASK` / `APPROVAL_AUTO` / `APPROVAL_FULL`),随对话上下文传到 `PermissionRuntime`,是所有工具确认闸的唯一判据;任何一档都只跳过"问"这一步,本地安全策略判定的硬拒绝仍然拒绝。"这次危不危险"由 `core/sandbox/permissions/models.py::danger_categories()` 从裁决理由里的 `danger:` 前缀还原,不靠匹配提示文案。读到早期版本存下的 `standard` / `readonly` 时按最保守的 `ask` 兜底。
渠道机器人、定时任务这类无人值守入口走 `default_allow` 分支,不受该档位影响。
@@ -143,17 +143,17 @@ SSE follower:chat_run_executor.follow_run_as_sse()
`create_agent_executor()` 是所有模式(主对话、计划、批量、子智能体、自动化)共用的工厂:
-- **MCP 工具**:经 catalog + 用户覆盖 + 请求上下文三层过滤后(见 [能力目录](catalog.md)),stable 服务复用进程级连接池(`core/llm/mcp_pool.py`),per-request 服务(如 `retrieve_dataset_content` 需带每请求 HTTP header)每次新建;用户自助添加的私有 MCP 按 owner 现查合入。
-- **技能**:经 `core/agent_skills/loader.py` 注册为 AgentScope Agent Skills,并放行 `view_text_file` 读取 SKILL.md(详见 [技能系统](agent-skills.md))。
+- **MCP 工具**:经 catalog + 用户覆盖 + 请求上下文三层过滤后(见 [能力目录](catalog.md)),stable 服务复用进程级连接池(`core/llm/mcp/`),per-request 服务(如 `retrieve_dataset_content` 需带每请求 HTTP header)每次新建;用户自助添加的私有 MCP 按 owner 现查合入。
+- **技能**:经 `core/capabilities/skills/loader.py` 注册为 AgentScope Agent Skills,并放行 `view_text_file` 读取 SKILL.md(详见 [技能系统](agent-skills.md))。
- **文件/沙箱工具**:`bash`、`sandbox_put_artifact`、`sandbox_get_artifact` 无条件注册;Read/Edit/Write/Glob/Grep/Delete/Move/mkdir + MySpace 工具受 `CODE_CAPABILITY_ENABLED` 门控,共享同一个 `ReadStateTracker` 维持「先 Read 才能 Edit」不变量。
-- **中间件**(洋葱模型,`core/llm/middlewares.py`):`DynamicModelMiddleware`(按 chat_mode 切模型,见 [模型接入](model-providers.md))、`FileContextMiddleware`(注入上传/历史文件上下文)、`SteerMiddleware`(工具结果之后、下一轮推理之前注入追加指令)、`WorkspacePinHintMiddleware`、`FinishPinGuardMiddleware`。
+- **中间件**(洋葱模型,`core/llm/runtime/`):`DynamicModelMiddleware`(按 chat_mode 切模型,见 [模型接入](model-providers.md))、`FileContextMiddleware`(注入上传/历史文件上下文)、`SteerMiddleware`(工具结果之后、下一轮推理之前注入追加指令)、`WorkspacePinHintMiddleware`、`FinishPinGuardMiddleware`。
- **上下文压缩**:`CompactingAgent` 把轮前、轮内和轮末三个触发时机接入同一个持久化 checkpoint 引擎,并共用 Codex 风格交接提示词;`ContextConfig` 只保留工具结果限长。压缩只使用统一协调器,不再运行框架的第二条摘要回退。压缩失败时保留原始上下文;最终请求仍超预算时保存断点并暂停,不继续调用回复模型。
- **权限**:所有已注册工具 seed 原生 `PermissionRule(ALLOW)`,保留 AgentScope 内置工具的危险操作检查(不使用一刀切 BYPASS)。
- **迭代上限**:主智能体默认 `max_iters=50`,隔离子智能体默认 10。
## SSE 事件类型与负载
-`orchestration/streaming.py::StreamingAgent` 把 AgentScope 2.0 `reply_stream` 的细粒度事件归并为内部事件;工具参数小分片按 256 字符或 50ms 合批,兼顾实时可见与 SSE 事件量。`workflow.py` 与 `chats.py::_stream_sse_response` 再补充会话级字段后落到 wire 上。前端实际收到的事件:
+`core/llm/runtime/streaming.py::StreamingAgent` 把 AgentScope 2.0 `reply_stream` 的细粒度事件归并为内部事件;工具参数小分片按 256 字符或 50ms 合批,兼顾实时可见与 SSE 事件量。`workflow.py` 与 `chats.py::_stream_sse_response` 再补充会话级字段后落到 wire 上。前端实际收到的事件:
| `type` | 含义 | 关键字段 |
|---|---|---|
@@ -200,7 +200,7 @@ data: {"type":"meta","route":"main","citations":[...],"usage":{"prompt_tokens":3
data: [DONE]
```
-`meta` 之后,`chat_run_executor.py` 持久化助手消息、回填 artifact,并起后台任务生成追问问题(`orchestration/followups.py`,结果写进消息 `extra_data.follow_up_questions`,前端经 `GET /v1/chats/{chat_id}/messages/{message_id}/followups` 拉取)。本体事件在前端汇总为独立的“领域本体治理”模块,不再写入或显示在“思考过程”中。模型草稿保持逐 token 流式展示;委员会仅在实际修订答案时发送一次 `content_replace`,前端原位替换正文,数据库只保存评审后的最终答案。`ontology_governance` 随助手消息持久化,刷新历史会话后仍可回显。
+`meta` 之后,`chat_run_executor.py` 持久化助手消息、回填 artifact,并起后台任务生成追问问题(`core/chat/followups.py`,结果写进消息 `extra_data.follow_up_questions`,前端经 `GET /v1/chats/{chat_id}/messages/{message_id}/followups` 拉取)。本体事件在前端汇总为独立的“领域本体治理”模块,不再写入或显示在“思考过程”中。模型草稿保持逐 token 流式展示;委员会仅在实际修订答案时发送一次 `content_replace`,前端原位替换正文,数据库只保存评审后的最终答案。`ontology_governance` 随助手消息持久化,刷新历史会话后仍可回显。
`usage` 是本轮所有模型调用的累计计费数据;上下文仪表使用的是
`context_usage`,即最近一次主模型调用的 `prompt_tokens + completion_tokens`,
@@ -233,9 +233,9 @@ data: [DONE]
引用让回答里的每个事实可溯源到具体工具结果。编号权收归后端唯一真源——模型只**复制**编号、不做任何计算,链路分四段:
-1. **发号回注(后端中间件)**:`core/llm/middlewares.py::CitationAnchorMiddleware` 挂在 AgentScope 2.0 的 `on_acting` 钩子上,在工具结果回给模型前调用 `orchestration/citation_anchor.py` 完成 提取 → 发号 → 回注:为每条可引用条目分配会话内单调唯一的锚点 id(`e1`、`e2`、…,跨工具、跨调用、跨轮不重复,新一轮从该会话历史消息的最大锚点续号),并把 `"cite_id": "e7"` 就地写进结果 JSON(纯文本结果在文末追加 `[cite_id: e7]` 行)。**发号器绑在 agent 实例上**(`attach_allocator()` / `resolve_allocator()`)——编排层与中间件由此共享同一个计数器;ContextVar 只作子智能体链路的兜底,因为 `astream_chat_workflow` 是 async generator,其上下文与 agent 实际执行所在的 task 并不互通。提取按四层降级:工具自声明 `__citations__` → 工具规格注册表(`TOOL_SPECS` 配置,列表路径 + 中英字段别名)→ 通用启发式(唯一字典数组字段)→ 整份结果 1 个锚点;操作型工具(写文件、pin 等,`SKIP_TOOLS`)直接放行。任何异常原样放行、绝不阻断对话。
+1. **发号回注(后端中间件)**:`core/llm/runtime/` 挂在 AgentScope 2.0 的 `on_acting` 钩子上,在工具结果回给模型前调用 `core/llm/citations/hook.py` 完成 提取 → 发号 → 回注:为每条可引用条目分配会话内单调唯一的锚点 id(`e1`、`e2`、…,跨工具、跨调用、跨轮不重复,新一轮从该会话历史消息的最大锚点续号),并把 `"cite_id": "e7"` 就地写进结果 JSON(纯文本结果在文末追加 `[cite_id: e7]` 行)。**发号器绑在 agent 实例上**(`attach_allocator()` / `resolve_allocator()`)——编排层与中间件由此共享同一个计数器;ContextVar 只作子智能体链路的兜底,因为 `astream_chat_workflow` 是 async generator,其上下文与 agent 实际执行所在的 task 并不互通。提取按四层降级:工具自声明 `__citations__` → 工具规格注册表(`TOOL_SPECS` 配置,列表路径 + 中英字段别名)→ 通用启发式(唯一字典数组字段)→ 整份结果 1 个锚点;操作型工具(写文件、pin 等,`SKIP_TOOLS`)直接放行。任何异常原样放行、绝不阻断对话。
2. **提示词约定**:系统提示词(`prompts/prompt_text/default/system/40_format.system.md` 的兜底版本,运行时以 DB 激活版本为准)只需一条与工具数量无关的通用规则:把结果里标注的 `cite_id` 原样复制进 `[锚文本](cite:e7)`(或句末 `[来源](cite:e7)`),禁止自行编号。
-3. **编排层消费**:每个 `tool_result` 事件经 `collect_citation_dicts()` 按 `tool_id` 从发号器注册表精确取 `CitationItem`(`id` / `tool_name` / `tool_id` / `title` / `url` / `snippet` / `source_type` / `item_index`);发号器缺位(旧对话回放等)时回退 `orchestration/citations.py` 的旧偏移提取。`source_type` 取值:CE 内置 `internet`、`knowledge_base`、`database`;`industry_news`、`ai_news`、`chain_info`、`company_profile` 等行业引用类型由商业版 EE 扩展。
+3. **编排层消费**:每个 `tool_result` 事件经 `collect_citation_dicts()` 按 `tool_id` 从发号器注册表精确取 `CitationItem`(`id` / `tool_name` / `tool_id` / `title` / `url` / `snippet` / `source_type` / `item_index`);发号器缺位(旧对话回放等)时回退 `core/llm/citations/` 的旧偏移提取。`source_type` 取值:CE 内置 `internet`、`knowledge_base`、`database`;`industry_news`、`ai_news`、`chain_info`、`company_profile` 等行业引用类型由商业版 EE 扩展。
4. **前端渲染**:citations 随 `tool_result` 与 `meta` 事件下发并随消息持久化(落库的就是注号后的结果,回放/分享与生成时编号一致)。`components/citation/CitationMarkdownBlock.tsx` 并行识别三种标记——`[锚文本](cite:eN)`(渲染为带悬浮出处卡片的文字链接)、`[[eN]]`(obsidian 双链容错)、旧格式 `[ref:工具名-序号]`(历史消息,渲染为角标);工具卡片条目上同步显示 `cite_id` 小徽章(`jx-tr-citeTag`),没有专属渲染器的工具由通用列表渲染器兜底成标准卡片。
**工具开发约定**:需要精确控制引用粒度的工具(自研或 MCP),在返回 JSON 里带 `__citations__` 字段——`[{"title": "...", "url": "...", "snippet": "...", "source_type": "..."}, …]`,条目顺序与结果正文对应;中间件优先采用并就地注入 `cite_id`。未自声明的工具按注册表配置或启发式提取,最差整份结果 1 个锚点——**任何工具默认可引用**。详见《[MCP 工具](mcp-tools.md)》的引用声明一节。
@@ -258,7 +258,7 @@ data: [DONE]
## 计划模式(Plan Mode)
-计划模式把复杂任务拆成「生成计划 → 用户确认/编辑 → 逐步执行」两阶段,实现在 `orchestration/subagents/plan_mode.py`:
+计划模式把复杂任务拆成「生成计划 → 用户确认/编辑 → 逐步执行」两阶段,实现在 `orchestration/plans/`:
- **生成**(`astream_generate_plan` / `POST /v1/plans/generate`):以 `disable_tools=True` 的"裸模型"产出结构化 JSON 计划。系统提示词解析顺序:版本池 `plan_mode` 激活版本 → 旧版 `system/90_plan_mode` 分段 → 文件兜底 `prompts/prompt_text/plan_mode/plan_mode.system.md` → 硬编码最小提示。
- **执行**(`astream_execute_plan` / `POST /v1/plans/{plan_id}/execute`):每个步骤独立建 agent 顺序执行,支持步骤级 MCP/技能/子智能体绑定、取消(`is_run_cancelled` 轮询);执行同样走 ChatRun + Redis Stream,可断线续播。
@@ -284,7 +284,7 @@ data: [DONE]
上下文和工作区是两个独立维度:探索员、审查员能检查当前文件,但不会被主对话或执行过程锚定;执行员则需要完整继承用户约束和既有决定。主智能体的动态路由表只展示本轮最终启用且角色策略允许下放的技能、MCP、知识库和基础工具;主智能体已关闭、未授权或被运行时过滤的能力既不会展示,也不会传给默认子智能体。三个角色都不能继续调用子智能体。默认列表不含 planner:普通主对话已用 `update_plan` 维护计划,避免形成“计划里的计划”。角色提示词在运行时仍来自版本池 `subagents` kind 的 `explorer / worker / reviewer` 三个独立 part,随包提示词 `extensions/agents/default//system.md` 只负责种子和故障回退;该 kind 保留在后端与迁移快照中,以兼容既有环境。
-除此之外,用户自建子智能体(`api/routes/v1/agents.py`,DB 表 `UserAgent`)可绑定独立的系统提示词、MCP / 技能 / 插件 / KB 集合与模型参数(provider / temperature / max_tokens / max_iters)。创建或编辑时,资源选择器支持以下来源:
+除此之外,用户自建子智能体(`edition_ee/routes/agents.py`,DB 表 `UserAgent`)可绑定独立的系统提示词、MCP / 技能 / 插件 / KB 集合与模型参数(provider / temperature / max_tokens / max_iters)。创建或编辑时,资源选择器支持以下来源:
- 已安装的技能与插件;
- 技能市场和插件市场,安装完成后自动绑定到当前子智能体;需要凭据的市场资源仍先走原有凭据配置与安装权限校验;
@@ -326,7 +326,7 @@ Enter / Tab 确认和 Escape 返回或关闭。个人在能力中心关闭的技
- **专属会话**:从子智能体详情页进入的会话使用 `agent_id`,后续轮次持续由该子智能体执行。
- **主智能体自主编排**:既没有结构化 `@`,也没有命中严格自然语言调用语法时,主智能体可
按任务需要调用
- `core/llm/subagent_tool.py` 注册的 `call_subagent`。子智能体在独立线程和事件循环中运行,结果
+ `core/llm/subagents/tool.py` 注册的 `call_subagent`。子智能体在独立线程和事件循环中运行,结果
回传给主智能体整合。这一路径适合多子智能体并行、任务拆分和跨领域汇总。
## 会话摘要与上下文压缩
@@ -335,8 +335,8 @@ Enter / Tab 确认和 Escape 返回或关闭。个人在能力中心关闭的技
| 层次 | 实现 | 触发 |
|---|---|---|
-| 会话标题摘要 | `core/llm/summarizer.py::ConversationSummarizer`(`summarizer` 模型角色,`ENABLE_SUMMARY` 开关),`POST /v1/summary` | 新会话标题自动生成 |
-| 统一上下文检查点 | `core/services/compaction_service.py::run_compaction()`;轮前、轮内、轮末共用同一触发比例、交接提示词、替代历史结构和持久化 checkpoint | 上下文达到模型窗口的配置比例时 |
+| 会话标题摘要 | `core/models/completions/summarizer.py::ConversationSummarizer`(`summarizer` 模型角色,`ENABLE_SUMMARY` 开关),`POST /v1/summary` | 新会话标题自动生成 |
+| 统一上下文检查点 | `core/chat/compaction/service.py::run_compaction()`;轮前、轮内、轮末共用同一触发比例、交接提示词、替代历史结构和持久化 checkpoint | 上下文达到模型窗口的配置比例时 |
| 确定性溢出保护 | `ContextConfig.tool_result_limit` 先限制单条工具结果;统一压缩失败时保留原始上下文,最终请求超预算则保存断点并暂停 | 单条工具结果过大,或统一压缩暂时不可用时 |
压缩只在合法边界切分:历史先按步骤展开,从尾部保留装得下
@@ -356,7 +356,7 @@ framing 的后端估算。当前回合结束后若后台压缩已启动,`meta.
## 超长工具结果 offload
-`core/llm/offloader.py::SandboxOffloader` 实现 AgentScope 2.0 `Offloader` 协议:上下文压缩/工具结果截断时,溢出部分不再被静默丢弃,而是落盘到沙箱 `/workspace/.offload/`(`tool_.txt` / `context_.txt`),框架会在给模型的 `` 里附上路径,模型可用 `Read` / `bash` 按需读回。仅在沙箱工具启用(`SANDBOX_TOOLS_ENABLED=true`,默认开)时挂载,写失败永不抛异常、返回降级说明。
+`core/llm/context/offloader.py::SandboxOffloader` 实现 AgentScope 2.0 `Offloader` 协议:上下文压缩/工具结果截断时,溢出部分不再被静默丢弃,而是落盘到沙箱 `/workspace/.offload/`(`tool_.txt` / `context_.txt`),框架会在给模型的 `` 里附上路径,模型可用 `Read` / `bash` 按需读回。仅在沙箱工具启用(`SANDBOX_TOOLS_ENABLED=true`,默认开)时挂载,写失败永不抛异常、返回降级说明。
## 会话分享(Chat Shares)
@@ -374,29 +374,29 @@ framing 的后端估算。当前回合结束后若后台压缩已启动,`meta.
## 其它入口
-同一编排底座还服务:消息重新生成(`POST /v1/chats/{chat_id}/regenerate`)、编辑重发(`POST /v1/chats/{chat_id}/edit`)、非流式 `POST /v1/agents/responses`(`stream: false`)与定时自动化(`orchestration/schedulers/`)。
+同一编排底座还服务:消息重新生成(`POST /v1/chats/{chat_id}/regenerate`)、编辑重发(`POST /v1/chats/{chat_id}/edit`)、非流式 `POST /v1/agents/responses`(`stream: false`)与定时自动化(`orchestration/automations/`)。
## 相关源码
| 主题 | 路径 |
|---|---|
| 聊天路由 / SSE 出口 | `src/backend/api/routes/v1/chats/__init__.py` |
-| Run 解耦 / Redis Stream / 续播 | `src/backend/orchestration/chat_run_executor.py`,`api/routes/v1/chat_runs.py` |
-| 流式编排主流程 | `src/backend/orchestration/workflow.py` |
-| 事件映射(reply_stream → SSE) | `src/backend/orchestration/streaming.py` |
+| Run 解耦 / Redis Stream / 续播 | `src/backend/core/chat/runtime/`,`api/routes/v1/chat_runs.py` |
+| 流式编排主流程 | `src/backend/core/agent_runtime/runner.py` |
+| 事件映射(reply_stream → SSE) | `src/backend/core/llm/runtime/streaming.py` |
| 运行时上下文装配 | `src/backend/core/chat/context.py` |
| Agent 工厂 | `src/backend/core/llm/factory/build.py` |
-| 中间件 | `src/backend/core/llm/middlewares.py`(纯函数 helper 在 `core/llm/hooks.py`) |
-| 引用抽取 | `src/backend/orchestration/citations.py` |
-| 跨会话引用(名片 / 摘要 / 分页) | `src/backend/core/services/chat_reference_service.py` |
+| 中间件 | `src/backend/core/llm/runtime/`(纯函数 helper 在 `core/harness/hooks.py`) |
+| 引用抽取 | `src/backend/core/llm/citations/` |
+| 跨会话引用(名片 / 摘要 / 分页) | `src/backend/core/chat/references.py` |
| 跨会话历史工具 | `src/backend/core/llm/tools/chat_history_tool.py` |
| 引用前端渲染 | `src/frontend/src/utils/citations.ts`,`src/frontend/src/components/citation/` |
-| 计划模式 | `src/backend/orchestration/subagents/plan_mode.py`,`api/routes/v1/plans.py` |
-| 子智能体工具 | `src/backend/core/llm/subagent_tool.py`,`api/routes/v1/agents.py` |
-| 标题摘要 / 上下文压缩 / 窗口保护 | `src/backend/core/llm/summarizer.py`、`compaction.py`、`core/services/compaction_service.py`、`context_manager.py` |
-| 超长结果 offload | `src/backend/core/llm/offloader.py` |
+| 计划模式 | `src/backend/orchestration/plans/`,`api/routes/v1/plans.py` |
+| 子智能体工具 | `src/backend/core/llm/subagents/tool.py`,`edition_ee/routes/agents.py` |
+| 标题摘要 / 上下文压缩 / 窗口保护 | `src/backend/core/models/completions/summarizer.py`、`compaction.py`、`core/chat/compaction/service.py`、`context_manager.py` |
+| 超长结果 offload | `src/backend/core/llm/context/offloader.py` |
| 会话分享 | `src/backend/api/routes/v1/chat_shares.py` |
-| 追问生成 | `src/backend/orchestration/followups.py` |
+| 追问生成 | `src/backend/core/chat/followups.py` |
| 前端流式解析 / 追加消息 | `src/frontend/src/hooks/chatStream.ts`,`useStreaming.ts`,`components/chat/QueuedMessageCard.tsx` |
### 桌面端工具权限与操作确认
diff --git a/document/zh-CN/modules/knowledge-base-wiki.md b/document/zh-CN/modules/knowledge-base-wiki.md
index f936e071..f37a6cda 100644
--- a/document/zh-CN/modules/knowledge-base-wiki.md
+++ b/document/zh-CN/modules/knowledge-base-wiki.md
@@ -138,12 +138,12 @@ Wiki 是纯离线批处理,调用量随文档量线性增长,**建议单独
| 层 | 位置 |
|---|---|
-| 数据模型 | `src/backend/core/db/models/kb_wiki.py` |
-| 生成管线 | `src/backend/core/kb/wiki/`(`extract` / `cite` / `dedup` / `taxonomy` / `reduce` / `finalize` / `pipeline`) |
-| 提示词 | `src/backend/core/kb/wiki/prompts.py` |
-| 作业与 worker | `src/backend/core/kb/wiki/jobs.py`、`worker.py` |
-| 读取面 | `src/backend/core/kb/wiki/local_provider.py` |
-| 按库分发 | `src/backend/core/kb/wiki_router.py` |
-| 路由 | `src/backend/api/routes/v1/kb_wiki.py` |
+| 数据模型 | `src/backend/features/knowledge/models.py` |
+| 生成管线 | `src/backend/features/knowledge/wiki/`(`extract` / `cite` / `dedup` / `taxonomy` / `reduce` / `finalize` / `pipeline`) |
+| 提示词 | `src/backend/features/knowledge/wiki/prompts.py` |
+| 作业与 worker | `src/backend/features/knowledge/wiki/jobs.py`、`worker.py` |
+| 读取面 | `src/backend/features/knowledge/wiki/local_provider.py` |
+| 按库分发 | `src/backend/features/knowledge/wiki_router.py` |
+| 路由 | `src/backend/features/knowledge/api/kb_wiki.py` |
| 智能体工具 | `src/backend/extensions/mcp_servers/retrieve_dataset_content_mcp/wiki_impl.py` |
| 前端 | `src/frontend/src/components/kb/`(`WikiPanel` / `WikiTree` / `ConceptGraph` / `IndexModePicker`) |
diff --git a/document/zh-CN/modules/knowledge-base.md b/document/zh-CN/modules/knowledge-base.md
index 15c3f525..5dd8ba6b 100644
--- a/document/zh-CN/modules/knowledge-base.md
+++ b/document/zh-CN/modules/knowledge-base.md
@@ -29,7 +29,7 @@ HugAgentOS 支持自建与外部知识库。公有检索按平台配置选择一
对象存储落盘(storage_key) │ · embed_text(query)
│ │ · Milvus hybrid_search:
▼ BackgroundTask │ 稠密(IP) + 稀疏(BoW) → RRF(k=60)
- core/content/kb_processing.py │ · 命中子块 → 回表 kb_chunks 取父块原文
+ core/files/content/kb_processing.py │ · 命中子块 → 回表 kb_chunks 取父块原文
vectorise_document_background() │ · 可选 Reranker 重排
· kb_parser.parse_and_chunk() │ · user_id 隔离 + public kb 全局可见
·(可选)LLM 抽关键词/生成问题 │
@@ -67,9 +67,9 @@ ORM 定义在 `src/backend/core/db/models/knowledge.py`:
| `kb_chunks` | **父块**原文(检索命中后返回给 LLM),含标签 `tags` 与关联问题 `questions` |
| `kb_assets` | 文档里的**媒体资产**(版面解析切出的图片、单独上传的图片):字节在对象存储,本表存归属分块、原文位置 `locator`、图注/OCR `text_content`、模型生成的描述 `caption` |
-Wiki 相关的三张表(`kb_wiki_pages` / `kb_wiki_folders` / `kb_wiki_jobs`)定义在 `core/db/models/kb_wiki.py`,见 [知识库 Wiki](./knowledge-base-wiki.md)。
+Wiki 相关的三张表(`kb_wiki_pages` / `kb_wiki_folders` / `kb_wiki_jobs`)定义在 `features/knowledge/models.py`,见 [知识库 Wiki](./knowledge-base-wiki.md)。
-子块不入关系库——向量化后写 Milvus collection `hugagent_kb_private`(`core/kb/kb_vector.py`),每行带 `user_id` / `kb_id` 字段做归属隔离,`row_type` 区分 chunk 行、question 行与 image 行。
+子块不入关系库——向量化后写 Milvus collection `hugagent_kb_private`(`features/knowledge/kb_vector.py`),每行带 `user_id` / `kb_id` 字段做归属隔离,`row_type` 区分 chunk 行、question 行与 image 行。
### 分块与索引
@@ -83,16 +83,16 @@ Wiki 相关的三张表(`kb_wiki_pages` / `kb_wiki_folders` / `kb_wiki_jobs`
| `recursive` | 递归定长切分 |
| `embedding_semantic` | 基于 embedding 相似度的语义边界检测 |
-父子分块参数可在上传时通过 `indexing_config` 调整:`parent_chunk_size`(默认 1024 token)、`child_chunk_size`(128)、`overlap_tokens`(20)、`parent_child_indexing`(默认 true)。还可启用 LLM 增强:`auto_keywords_count`(每父块抽关键词入 tags,参与稀疏检索)与 `auto_questions_count`(每父块生成关联问题,作为独立 question 行入 Milvus,提高问句召回)。后台向量化任务在 `core/content/kb_processing.py::vectorise_document_background()`。
+父子分块参数可在上传时通过 `indexing_config` 调整:`parent_chunk_size`(默认 1024 token)、`child_chunk_size`(128)、`overlap_tokens`(20)、`parent_child_indexing`(默认 true)。还可启用 LLM 增强:`auto_keywords_count`(每父块抽关键词入 tags,参与稀疏检索)与 `auto_questions_count`(每父块生成关联问题,作为独立 question 行入 Milvus,提高问句召回)。后台向量化任务在 `core/files/content/kb_processing.py::vectorise_document_background()`。
### 多模态:图片检索
-文档里的图不再被丢弃。索引时走 `core/kb/kb_assets.py`:
+文档里的图不再被丢弃。索引时走 `features/knowledge/kb_assets.py`:
1. **抽图**——PDF 经外部解析服务的版面切分拿回图片本体、图注、页码与 bbox;单独上传的
图片自身即一个资产(此前会被当作纯文本硬解成乱码入库,现已修复)。字节存对象存储,
正文里原本指向解析服务临时路径的死链被改写成真实资产 URL。
-2. **图像理解**——复用[视觉桥](./model-providers.md#视觉桥让纯文本模型能读图)(`core/vision`),和对话里「纯文本主模型读图」
+2. **图像理解**——复用[视觉桥](./model-providers.md#视觉桥让纯文本模型能读图)(`core/models/vision`),和对话里「纯文本主模型读图」
走的是同一条通路、同一个 `vision` 模型角色,因此白得三样东西:按图片内容哈希的**证据缓存**
(重新索引同一篇文档不用再付一次钱)、三协议与结构化输出降级、以及「该角色只能指派多模态模型」
的硬约束。产出映射:`summary` → `caption`,`ocr.full_text` 并入 `text_content`(与原图注共存),
@@ -134,7 +134,7 @@ ASR 转写写进 `text_content`,其后的索引与检索完全复用。
### API 路由
-用户侧路由前缀 `/v1/catalog/kb`(`src/backend/api/routes/v1/kb.py`,请求模型在 `kb_models.py`):
+用户侧路由前缀 `/v1/catalog/kb`(`src/backend/features/knowledge/api/kb.py`,请求模型在 `kb_models.py`):
| 方法 | 路径 | 说明 |
|---|---|---|
@@ -150,7 +150,7 @@ ASR 转写写进 `text_content`,其后的索引与检索完全复用。
| GET / PATCH | `/v1/catalog/kb/{kb_id}/chunks[/{chunk_id}]` | 分块列表 / 编辑标签与问题 |
| GET | `/v1/catalog/kb/assets/{asset_id}` | 读取媒体资产原始字节(可见性跟随所属空间) |
-业务逻辑集中在 `core/services/kb_service.py::KBService`。
+业务逻辑集中在 `features/knowledge/service.py::KBService`。
### 系统托管知识库:我的空间同步
@@ -158,13 +158,13 @@ ASR 转写写进 `text_content`,其后的索引与检索完全复用。
### 管理员公共知识库(商业版 EE)
-`/v1/admin/kb/*` 管理台路由在 `src/backend/api/routes/v1/admin_kb.py`,挂 `content_admin` 能力位(EE 路由表见 `api/routes/v1/__init__.py`)。公共知识库由合成系统账号 `system_public_kb` 持有(`kb_service.py::SYSTEM_KB_OWNER_ID`),`visibility=public`,对全体用户可见可检索。管理端额外提供原始文件下载、Office 转 PDF 在线预览、分块内容编辑 / 删除等能力。
+`/v1/admin/kb/*` 管理台路由在 `src/backend/edition_ee/routes/admin_kb.py`,挂 `content_admin` 能力位(EE 路由表见 `api/routes/v1/__init__.py`)。公共知识库由合成系统账号 `system_public_kb` 持有(`kb_service.py::SYSTEM_KB_OWNER_ID`),`visibility=public`,对全体用户可见可检索。管理端额外提供原始文件下载、Office 转 PDF 在线预览、分块内容编辑 / 删除等能力。
公有知识库检索 `retrieve_dataset_content` 严格按平台配置选择一种来源:自建模式(`knowledge_base.provider=custom`)只检索当前用户有权访问的本地公有/共享库,排除个人私有库;外部模式只检索当前选择的 Dify、FastGPT 或 WeKnora 数据集,连接配置也按后端隔离。两种来源不合并,空结果或服务错误均不触发跨来源回退。公有知识库列表遵循相同的来源选择;私有知识库仍通过独立的 `retrieve_local_kb` 入口访问。
## Dify 外接知识库(商业版 EE)
-商业版客户端位于 `src/backend/edition_ee/kb/dify.py`,共享路由通过 `core/kb/external_provider.py` 接缝调用。CE 派生树把该接缝替换为禁用实现,且不包含 Dify 客户端。启用判定 `is_dify_enabled()` 的优先级:
+商业版客户端位于 `src/backend/edition_ee/kb/dify.py`,共享路由通过 `features/knowledge/extension.py` 接缝调用。CE 派生树把该接缝替换为禁用实现,且不包含 Dify 客户端。启用判定 `is_dify_enabled()` 的优先级:
1. DB 系统配置 `knowledge_base.provider == "dify"`(Config 管理台可改);
2. 环境变量 `KNOWLEDGE_BASE=dify`;
@@ -180,9 +180,9 @@ DIFY_API_KEY=dataset-... # 兼容别名 DIFY_AUTH_TOKEN
## 文件解析支持
-知识库上传校验在 `core/content/file_validation.py::validate_kb_file`(扩展名 + magic bytes 双重校验),允许:`.pdf` `.txt` `.md` `.doc` `.docx` `.xls` `.xlsx` `.csv` `.json` 及图片(`.png` `.jpg` `.jpeg` `.webp` `.gif`)。
+知识库上传校验在 `core/files/content/file_validation.py::validate_kb_file`(扩展名 + magic bytes 双重校验),允许:`.pdf` `.txt` `.md` `.doc` `.docx` `.xls` `.xlsx` `.csv` `.json` 及图片(`.png` `.jpg` `.jpeg` `.webp` `.gif`)。
-通用文件解析器 `core/content/file_parser.py::parse_file()` 覆盖更广(对话附件、我的空间文件共用):PDF、DOCX、DOC/WPS(经 LibreOffice 转换)、TXT、XLSX/XLS、CSV、PPTX,外加 HTML / Markdown / JSON / YAML / 代码等纯文本格式直接 UTF-8 解码。
+通用文件解析器 `core/files/content/file_parser.py::parse_file()` 覆盖更广(对话附件、我的空间文件共用):PDF、DOCX、DOC/WPS(经 LibreOffice 转换)、TXT、XLSX/XLS、CSV、PPTX,外加 HTML / Markdown / JSON / YAML / 代码等纯文本格式直接 UTF-8 解码。
## 前端
@@ -196,17 +196,17 @@ DIFY_API_KEY=dataset-... # 兼容别名 DIFY_AUTH_TOKEN
| 路径 | 职责 |
|---|---|
-| `src/backend/core/kb/kb_parser.py` | 文档解析 + 父子分块(5 种 chunk_method) |
-| `src/backend/core/kb/kb_vector.py` | Milvus collection、embedding、混合检索、重排 |
-| `src/backend/core/kb/kb_assets.py` | 媒体资产:落盘、图像理解(调用视觉桥)、检索行、结果附图 |
+| `src/backend/features/knowledge/kb_parser.py` | 文档解析 + 父子分块(5 种 chunk_method) |
+| `src/backend/features/knowledge/kb_vector.py` | Milvus collection、embedding、混合检索、重排 |
+| `src/backend/features/knowledge/kb_assets.py` | 媒体资产:落盘、图像理解(调用视觉桥)、检索行、结果附图 |
| `src/backend/edition_ee/kb/dify.py` | Dify datasets 客户端与启用判定(仅 EE) |
-| `src/backend/core/kb/external_provider.py` | 版本中立的外部知识库接缝;CE overlay 将其禁用 |
-| `src/backend/core/content/kb_processing.py` | 后台向量化任务、LLM 关键词 / 问题增强 |
-| `src/backend/core/content/file_validation.py` | 上传文件校验(扩展名 + magic bytes) |
-| `src/backend/core/content/file_parser.py` | 通用文件解析器 |
-| `src/backend/core/services/kb_service.py` | 知识库业务逻辑(含系统托管同步库) |
-| `src/backend/api/routes/v1/kb.py` + `kb_models.py` | 用户侧 `/v1/catalog/kb` 路由 |
-| `src/backend/api/routes/v1/admin_kb.py` | 管理台公共知识库路由(商业版 EE) |
+| `src/backend/features/knowledge/extension.py` | 版本中立的外部知识库接缝;CE overlay 将其禁用 |
+| `src/backend/features/knowledge/processing.py` | 后台向量化任务、LLM 关键词 / 问题增强 |
+| `src/backend/core/files/content/file_validation.py` | 上传文件校验(扩展名 + magic bytes) |
+| `src/backend/core/files/content/file_parser.py` | 通用文件解析器 |
+| `src/backend/features/knowledge/service.py` | 知识库业务逻辑(含系统托管同步库) |
+| `src/backend/features/knowledge/api/kb.py` + `kb_models.py` | 用户侧 `/v1/catalog/kb` 路由 |
+| `src/backend/edition_ee/routes/admin_kb.py` | 管理台公共知识库路由(商业版 EE) |
| `src/backend/api/routes/v1/catalog.py` | 能力目录聚合(CE 仅私有库;EE 追加 Dify / 公共库) |
| `src/backend/extensions/mcp_servers/retrieve_dataset_content_mcp/` | 检索 MCP server(两个工具) |
| `src/backend/core/db/models/knowledge.py` | `KBSpace` / `KBDocument` / `KBChunk` / `KBAsset` ORM |
diff --git a/document/zh-CN/modules/mcp-tools.md b/document/zh-CN/modules/mcp-tools.md
index ad8af2da..44441e1f 100644
--- a/document/zh-CN/modules/mcp-tools.md
+++ b/document/zh-CN/modules/mcp-tools.md
@@ -26,7 +26,7 @@ HugAgentOS 的工具生态构建在 [MCP(Model Context Protocol)](https://mo
│ agent) │ │ │ :9103 产业知识中心统一 MCP(插件,EE) │
└─────────┘ │ │ :9104 保留(图表 MCP 已退役) │
│ │ │ :9105 保留(原报告导出 MCP) │
- MCPConnectionPool │ │ :9106 web_fetch(网页抓取) │
+ Native MCP Client │ │ :9106 web_fetch(网页抓取) │
mcp_pool.py) └───┬────┘ :9108 automation_task(自动化插件提供) │
│ 9109–9111 保留(原 excel/ppt/pdf MCP) │
│ :9112 skill_manager(技能管理) │
@@ -75,14 +75,16 @@ HugAgentOS 的工具生态构建在 [MCP(Model Context Protocol)](https://mo
### internet_search — 联网搜索(CE)
`internet_search(query, queries, max_results, topic, search_depth, include_raw_content)`
-通过 `INTERNET_SEARCH_ENGINE` 选择 Tavily、百度或 LangSearch,只读取所选引擎的 API Key。
+通过 `INTERNET_SEARCH_ENGINE` 选择 Tavily、百度、LangSearch 或 DeepSeek,只读取所选引擎的 API Key。
单个 `query` 与 `queries` 二选一;批量最多 4 个不同查询并发,按输入查询顺序轮流取结果,
按 URL 去重后返回最多 8 条来源。默认返回 5 条,复杂调研可显式设置 `max_results=8`。
不限制来源语言;Tavily 国家偏好仅在显式配置且 `topic=general` 时发送,没有默认中国偏好。
公开资料和技术调研可直接使用;内部业务问题优先内部权威来源。摘要不足以支持关键结论时,
可自主调用 `web_fetch` 核验原文。`topic`、`search_depth`、原始正文参数由 Tavily 支持;
-LangSearch 的 summary 映射为 content。
+LangSearch 的 summary 映射为 content。DeepSeek 的 summary 映射为 content,`include_raw_content`
+映射为页面正文;单个查询最长 128 字符(超出返回 `query_too_long`);`topic=news` 时用 `after:`
+限定最近 30 天(无日期页面会被排除);`rerank_score` 低于 0.5 的结果会被丢弃。
来源统一保存在 `result.results`,包含标题、URL、摘要、可用发布时间、获取时间和命中查询序号。
百度日期原值以 `date_raw` 保留,未知日期不会替换为抓取时间。`content_truncated` 表示内容裁剪,
@@ -190,7 +192,7 @@ extensions/mcp_servers/_mcp/
- 中间件(`CitationAnchorMiddleware`)会**优先采用**该声明,就地为每条注入
`"cite_id": "eN"`,模型引用时原样复制。
2. **标准列表结构 → 加一行注册表配置**:结果形如 `{"items": [{"title": …, "content": …}]}`
- 的列表型工具,在 `orchestration/citation_anchor.py::TOOL_SPECS` 里登记
+ 的列表型工具,在 `core/llm/citations/hook.py::TOOL_SPECS` 里登记
`items_paths` + 字段别名即可逐条编号,不改工具代码。
3. **什么都不做 → 自动兜底**:通用启发式能识别顶层(或 `result` 下一层)唯一的
字典数组字段并逐条编号;彻底认不出时整份结果作为 1 个锚点。
@@ -201,21 +203,18 @@ extensions/mcp_servers/_mcp/
Server 同样适用(返回 JSON 即可)。回注发生在结果进模型上下文与落库之前,前端工具卡片
会把 `cite_id` 渲染成条目徽章,与正文 `[锚文本](cite:eN)` 引用一一对应。
-## 后端客户端:连接池与裸名还原
+## 后端客户端:原生 MCP 生命周期
-后端基于 AgentScope 2.0 的 `MCPClient` 连接 MCP Server,核心在两个文件:
+后端使用 MCP SDK 2.3 的 `Client(mode="auto")` 和 AgentScope `ToolBase`。`client.py` 处理传输授权,`lifetime.py` 管理 SDK 上下文并完整遍历分页。HTTP 在一次运行内复用客户端。STDIO 按执行用户、聊天和配置独占租用,成功结束后保留 60 秒;失败或取消则销毁。已删除稳定连接开关和启动连接池。
-- **`core/llm/mcp_pool.py` — `MCPConnectionPool`**(进程级单例):启动时 `warmup_mcp_tools()` 从 DB 读取全部启用的 Server 配置并预连接。注意 2.0 语义下的池化策略:
- - **stdio 且 `is_stable=true`** 的 Server 跨请求保持连接(省掉 1–7 秒子进程冷启动);
- - **HTTP Server 一律不池化**——2.0 的 stateful HTTP 客户端与 asyncio task 绑定,跨请求复用会触发 cancel-scope 崩溃,因此每请求用 `is_stateful=False` 新建连接;
- - per-request Server(知识库检索带用户头)每次现连,请求结束 `close_transient()` 关闭。
-- **`core/llm/mcp_manager.py` — `BareNameMCPClient`**:AgentScope 2.0 默认把工具名改写为 `mcp____`,该子类还原为服务器侧裸名(`internet_search` 而非 `mcp__internet_search__internet_search`),保证展示名映射(`core/config/display_names.py`)、[引用溯源](chat.md)的按工具名分发、前端图标渲染等 1.x 约定继续成立。
+`callbacks.py` 将隔离的文本 Sampling 与用户 Elicitation 绑定到当前运行。资源、URI 模板和提示词转换为受权限控制的工具。进度显示在现有工具卡片。结构化输出和资源链接保留在工具 metadata 与模型输出中。SDK 2.3 尚未提供新版 Tasks 扩展。
+
+MCP 工具的执行预算不包含等待用户回答的时间。有效提问会暂停当前调用的计时;回答或放弃后,从剩余预算继续。多次、重叠提问不会重置预算,其他调用继续独立计时。用户交互仍受现有等待期限约束,取消运行会清理待回答问题。SDK 请求和 HTTP 读取由调用预算约束,连接、写入和发现仍保留超时。
-`Toolkit` 在 2.0 是一次性构造,由 `core/llm/factory/build.py` 统一执行 `Toolkit(tools=[...], mcps=clients)`。
## 统一工具权限网关
-`core/llm/tool_permissions.py` 提供运行级 `ToolPermissionRegistry` 与
+`core/llm/tool_permissions/` 提供运行级 `ToolPermissionRegistry` 与
`ToolPermissionMiddleware`。Registry 是**受控工具清单**,不是全工具声明表:
- 工具名没有进入 Registry 时保持既有行为,直接放行,不要求新工具声明权限;
@@ -228,7 +227,7 @@ Server 同样适用(返回 JSON 即可)。回注发生在结果进模型上
一律拒绝,不会再以“记录审计后继续执行”降级;
- 渐进加载插件把新出现的 MCP 工具加入同一个运行级 Registry,因此热加载不会绕开网关。
-原生工具可以在 `ToolCollector.register_tool_function(..., permission=spec)` 注册规则;平台
+原生工具可以在 `ToolAssembly.append(function_tool(..., dispatch_spec=spec))` 注册规则;平台
内置的本机文件、Bash 与 MySpace 工具集中列在 `builtin_tool_permission()`。没有传
`permission` 且不在该表中的原生工具不受网关管理。
@@ -251,7 +250,7 @@ MCP 规则只接受平台/管理员持有的可信连接配置,不采信远端
## 配置注册:DB 驱动 + catalog 门控
-MCP Server 的配置真源是数据库表 `admin_mcp_servers`(ORM:`core/db/models.py::AdminMcpServer`),由 `core/services/mcp_service.py::McpServerConfigService` 以 30 秒 TTL 缓存读出,格式与旧 `MCP_SERVERS` dict 兼容(`transport / command / args / env / url / headers / is_stable`)。`core/config/mcp_config.py` 保留为内置 Server 的 URL 构造器(首次部署种子)。
+MCP Server 的配置真源是数据库表 `admin_mcp_servers`(ORM:`core/db/models/`),由 `core/capabilities/mcp/service.py::McpServerConfigService` 以 30 秒 TTL 缓存读出,使用原生客户端配置(`transport / command / args / env / url / headers`)。`core/config/mcp_config.py` 保留为内置 Server 的 URL 构造器(首次部署种子)。
是否**对模型可见**还要过一道 [catalog](catalog.md) 门控:`core/config/catalog.json` 中 `mcp` 段的每项对应一个 server_id,`is_enabled(id, "mcp_server")` 为 false 的 Server 即使连接着也不会注册给智能体。
@@ -263,7 +262,7 @@ MCP Server 的配置真源是数据库表 `admin_mcp_servers`(ORM:`core/db/m
- **创建即试连**:`_probe_connectivity` 真实连一次,失败拒绝落库;
- **开关与排序**:`POST /{id}/toggle` 即时启停(联动刷新 catalog 与连接池);
- **密钥保护**:HTTP Header 值加密存储、接口统一以 `***` 返回;`env_vars` 中疑似密钥的值同样脱敏;
-- **测试与重载**:`POST /{id}/test` 单独试连;`POST /reload-pool` 热重建连接池;
+- **测试与重载**:`POST /{id}/test` 单独试连;`配置更新自动失效缓存` 更新后下一次运行重新发现;
- **移至 MCP 市场**:把现有远程 HTTP/SSE MCP 生成无凭据的市场快照,
并停用原全局实例;该服务随后从 MCP 服务管理列表移除,可在市场中复核、
编辑展示信息,再按需全局安装。原服务已配置的 Token 仍加密保留在服务端,
@@ -279,7 +278,7 @@ MCP Server 的配置真源是数据库表 `admin_mcp_servers`(ORM:`core/db/m
- `POST /v1/me/mcp-servers`:添加私有远程 MCP,支持公网 HTTP/HTTPS 上的 HTTP/SSE——用户入口禁止 stdio,避免在服务器执行任意命令;地址会经过 DNS/IP 检查以阻断 localhost、内网、链路本地与保留地址;创建即试连,连不上不落库。HTTP 为明文传输,生产环境仍建议使用 HTTPS;
- `DELETE /v1/me/mcp-servers/{id}`:删除自己的私有 MCP。
-实现上复用同一张 `admin_mcp_servers` 表:`owner_user_id` = 当前用户实现 owner 隔离,server_id 自动生成 `umcp_` 防冲突,`is_stable=False` 不进 warmup 池。HTTP Header 值使用 Fernet 加密后落库,运行时才解密。该功能由管理台的 per-user 权限位 `can_add_mcp` 控制(社区版单租户默认即可放开;商业版由组织管理员按用户授予,见[版本说明](../editions/overview.md))。
+实现上复用同一张 `admin_mcp_servers` 表:`owner_user_id` = 当前用户实现 owner 隔离,server_id 自动生成 `umcp_` 防冲突。HTTP Header 值使用 Fernet 加密后落库,运行时才解密。该功能由管理台的 per-user 权限位 `can_add_mcp` 控制(社区版单租户默认即可放开;商业版由组织管理员按用户授予,见[版本说明](../editions/overview.md))。
## MCP 市场
@@ -359,19 +358,19 @@ docker-compose up -d --build mcp
| `src/backend/extensions/mcp_servers/_serve.py` | stdio / streamable-http 双 transport 统一入口 |
| `src/backend/extensions/mcp_servers/registry.py` | 安装位置、暴露方式、包名和基础端口 |
| `src/backend/extensions/mcp_servers/_ports.py` | 实际部署端口映射 |
-| `src/backend/core/llm/mcp_pool.py` | MCP 连接池(stdio 池化 / HTTP per-request) |
-| `src/backend/core/llm/mcp_manager.py` | MCPClient 构造 + 工具裸名还原 |
-| `src/backend/core/llm/tool_permissions.py` | 受控工具 Registry、权限判定、确认路由与执行票据 |
-| `src/backend/core/services/mcp_service.py` | DB 驱动的 Server 配置服务(30s 缓存) |
-| `src/backend/core/services/mcp_marketplace_service.py` | 市场发布、审核、安装、可见性与安全控制 |
-| `src/backend/core/services/mcp_oauth_service.py` | OAuth 2.1 登录、SDK 元数据发现、加密令牌存储与刷新 |
-| `src/backend/core/services/mcp_marketplace_monitor.py` | 远程工具快照定期复检与漂移监控 |
+| `src/backend/core/llm/mcp/` | MCP 连接池(stdio 池化 / HTTP per-request) |
+| `src/backend/core/llm/mcp/manager.py` | MCPClient 构造 + 工具裸名还原 |
+| `src/backend/core/llm/tool_permissions/` | 受控工具 Registry、权限判定、确认路由与执行票据 |
+| `src/backend/core/capabilities/mcp/service.py` | DB 驱动的 Server 配置服务(30s 缓存) |
+| `src/backend/features/marketplace/` | 市场发布、审核、安装、可见性与安全控制 |
+| `src/backend/core/capabilities/mcp/oauth.py` | OAuth 2.1 登录、SDK 元数据发现、加密令牌存储与刷新 |
+| `src/backend/features/marketplace/` | 远程工具快照定期复检与漂移监控 |
| `src/backend/core/config/mcp_config.py` | 内置 Server URL 构造(http://mcp:NNNN/mcp/) |
| `src/backend/core/config/catalog.json` | 能力目录:MCP 启停门控种子 |
| `src/backend/api/routes/v1/admin_mcp_servers.py` | 管理员自定义 MCP API |
| `src/backend/api/routes/v1/me_capabilities.py` | 用户自助私有 MCP / 技能 API |
-| `src/backend/api/routes/v1/mcp_marketplace.py` | 用户端 MCP 市场 API |
-| `src/backend/api/routes/v1/admin_mcp_marketplace.py` | 管理端 MCP 市场与审核 API |
+| `src/backend/features/marketplace/api/mcp_marketplace.py` | 用户端 MCP 市场 API |
+| `src/backend/features/marketplace/api/admin_mcp_marketplace.py` | 管理端 MCP 市场与审核 API |
| `docker/Dockerfile.mcp` | mcp 容器镜像(MCP 运行时、绘图依赖与中文字体) |
相关文档:[能力目录](catalog.md) · [技能系统](agent-skills.md) · [知识库](knowledge-base.md) · [版本与许可](../editions/overview.md)
diff --git a/document/zh-CN/modules/memory.md b/document/zh-CN/modules/memory.md
index 98d7822f..c596185d 100644
--- a/document/zh-CN/modules/memory.md
+++ b/document/zh-CN/modules/memory.md
@@ -64,7 +64,7 @@ save_memories_background()
· sanitize 脱敏闸门 → 写 L1/L2/L3/Session → audit 旁路
```
-检索与注入的整合层在 `src/backend/orchestration/memory_integration.py`;mem0 的 LLM / Embedder / Milvus / Reranker 配置在 `src/backend/core/memory/service.py`,Neo4j 图谱读写在 `src/backend/core/memory/graph.py`。模型配置优先取 DB 中 `memory` / `embedding` 角色,缺省回落到环境变量。
+检索与注入的整合层在 `src/backend/core/agent_runtime/integrations/memory.py`;mem0 的 LLM / Embedder / Milvus / Reranker 配置在 `src/backend/core/memory/service.py`,Neo4j 图谱读写在 `src/backend/core/memory/graph.py`。模型配置优先取 DB 中 `memory` / `embedding` 角色,缺省回落到环境变量。
## 写入流水线与抽取器
@@ -136,7 +136,7 @@ save_memories_background()
| `memory_enabled` | 永久记忆**读取**:会话启动时是否注入冻结块 | `false` |
| `memory_write_enabled` | **写入**:对话结束后是否抽取并保存记忆 | `false` |
-项目对话有独立作用域:个人项目 / 默认空间用真实 `user_id`,团队项目用 `scope_user_id = "team:"`——同团队成员写入同一个 mem0 桶实现共享,真实作者保留在 `metadata.author_user_id`(见 `orchestration/memory_integration.py::save_memories_background` 与 `api/routes/v1/memories.py::list_memories`)。项目级开关存于 `projects.metadata`(`memory_enabled` / `memory_write_enabled`,项目内缺省 `true`),详见 [项目空间与我的空间](./projects-myspace.md)。
+项目对话有独立作用域:个人项目 / 默认空间用真实 `user_id`,团队项目用 `scope_user_id = "team:"`——同团队成员写入同一个 mem0 桶实现共享,真实作者保留在 `metadata.author_user_id`(见 `core/agent_runtime/integrations/memory.py::save_memories_background` 与 `api/routes/v1/memories.py::list_memories`)。项目级开关存于 `projects.metadata`(`memory_enabled` / `memory_write_enabled`,项目内缺省 `true`),详见 [项目空间与我的空间](./projects-myspace.md)。
## 前端记忆中心
@@ -229,8 +229,8 @@ RERANKER_API_KEY=...
| `src/backend/core/memory/sanitizer.py` | 脱敏闸门(硬编码规则 + DB 动态规则) |
| `src/backend/core/memory/audit.py` | 审计旁路(商业版 EE) |
| `src/backend/core/memory/context.py` | `MemoryContext` 与 workspace / 层级解析 |
-| `src/backend/orchestration/memory_integration.py` | 检索启动、冻结块组装与注入、保存转调 |
-| `src/backend/orchestration/workflow.py` | 主编排:记忆 hook 接线点 |
+| `src/backend/core/agent_runtime/integrations/memory.py` | 检索启动、冻结块组装与注入、保存转调 |
+| `src/backend/core/agent_runtime/runner.py` | 主编排:记忆 hook 接线点 |
| `src/backend/api/routes/v1/memories.py` | `/v1/memories` 管理 API |
| `src/backend/core/db/models/memory.py` | `ProfileMemory`、`MemoryOutbox`、`MemorySanitizerRule` ORM |
| `src/backend/edition_ee/db/models/memory.py` | `MemoryAudit` ORM(仅 EE) |
diff --git a/document/zh-CN/modules/model-providers.md b/document/zh-CN/modules/model-providers.md
index 6a048edf..40e28d2a 100644
--- a/document/zh-CN/modules/model-providers.md
+++ b/document/zh-CN/modules/model-providers.md
@@ -9,7 +9,7 @@ HugAgentOS 通过 **OpenAI 兼容协议**接入任意大模型端点(vLLM、Ol
## 配置模型:供应商 + 角色
-两张表(`core/db/models.py`):`model_providers`(base_url / api_key / model_name / extra_config / is_active)与 `model_role_assignments`(role_key → provider_id)。角色定义在 `core/db/model_repository.py::ROLE_DEFINITIONS`:
+两张表(`core/db/models/`):`model_providers`(base_url / api_key / model_name / extra_config / is_active)与 `model_role_assignments`(role_key → provider_id)。角色定义在 `core/models/repository.py::ROLE_DEFINITIONS`:
| role_key | 用途 | 类型 |
|---|---|---|
@@ -36,6 +36,8 @@ HugAgentOS 通过 **OpenAI 兼容协议**接入任意大模型端点(vLLM、Ol
### 接口协议:默认 Responses,不支持才回退
+Responses 的思考原文和摘要事件由当前锁定的 AgentScope 原生解析。平台原样保留事件的 `item_id` 与分段索引,避免流式发送失败,以及不同思考条目或分段被错误合并。
+
OpenAI 兼容上游有两套接口:较老的 `POST /chat/completions` 和较新的 `POST /responses`。平台**默认走 Responses**,因为它把模型的思考做成独立的 `reasoning` 条目,下一轮可原样回传;`/chat/completions` 没有放思考的位置,只能把思考夹带进 `reasoning_content` 字段。只有确实没有 `/responses` 路由的上游(常见于只做转发的中转网关)才使用 Chat Completions。
走哪套由 `extra_config.api_protocol` 决定,取值 `responses` / `chat_completions`。该值不靠厂商名推断,而是保存供应商时向上游探测后写入。探测分两步,两步都过才判为 Responses 可用:
@@ -124,7 +126,7 @@ DeepSeek、GLM、Qwen 文本版这类主力模型看不见图片。视觉桥的
## 动态模型切换(chat_mode)
-前端每条消息可带 `chat_mode`(`turbo / fast / medium / high / max`),在 reply 开始前由 `core/llm/middlewares.py::DynamicModelMiddleware`(`on_reply`)热切 `agent.model`:
+前端每条消息可带 `chat_mode`(`turbo / fast / medium / high / max`),在 reply 开始前由 `core/llm/runtime/`(`on_reply`)热切 `agent.model`:
```
chat_mode → hooks._resolve_chat_mode(agent.state)
@@ -134,7 +136,7 @@ chat_mode → hooks._resolve_chat_mode(agent.state)
high/max → reasoning_effort=high/max(端点须声明支持)
```
-旧客户端只传 `enable_thinking` 时回退映射为 medium / fast。这就是 1.x「pre_reply 动态模型 hook」在 AgentScope 2.0 下的形态——hook 工厂已重构为中间件,`core/llm/hooks.py` 仅保留纯函数 helper。
+旧客户端只传 `enable_thinking` 时回退映射为 medium / fast。这就是 1.x「pre_reply 动态模型 hook」在 AgentScope 2.0 下的形态——hook 工厂已重构为中间件,`core/harness/hooks.py` 仅保留纯函数 helper。
## 环境变量与 MCP 子进程注入
@@ -147,7 +149,7 @@ chat_mode → hooks._resolve_chat_mode(agent.state)
## 个人 API-Key
-用户可签发个人密钥以编程方式调用平台 API(`api/routes/v1/api_keys.py` + `core/services/api_key_service.py`):
+用户可签发个人密钥以编程方式调用平台 API(`api/routes/v1/api_keys.py` + `core/auth/api_keys.py`):
- 明文形如 `sk-jx-`,**仅创建时返回一次**,DB 只存 SHA256 哈希 + 前缀;支持 7/30/90/180/365 天有效期或永不过期、启停与撤销。
- 调用方式 `Authorization: Bearer sk-jx-...`,鉴权层(`core/auth/backend.py`)识别 key 前缀后经 `resolve_api_key` 解析为用户上下文,校验启用/未撤销/未过期。
@@ -157,7 +159,7 @@ chat_mode → hooks._resolve_chat_mode(agent.state)
## 计费与用量
-token 用量在流式收尾的 `meta.usage` 中统计(`orchestration/streaming.py` 从 `ModelCallEndEvent` 累计)并随消息持久化,之上有两组管理台报表(`CONFIG_TOKEN`):
+token 用量在流式收尾的 `meta.usage` 中统计(`core/llm/runtime/streaming.py` 从 `ModelCallEndEvent` 累计)并随消息持久化,之上有两组管理台报表(`CONFIG_TOKEN`):
- **用量日志** `api/routes/v1/admin_usage_logs.py`:`GET /v1/admin/usage-logs`(明细)、`/summary`(汇总)、`/models`(去重模型名)。
- **计费报表** `api/routes/v1/admin_billing.py`(**商业版 EE**——完整管理台能力):`GET /v1/admin/billing/summary`(按用户/模型聚合成本)、模型定价 CRUD(`/pricing`,输入/输出单价、币种)、`GET /v1/admin/billing/export`(CSV 成本导出)。
@@ -166,21 +168,21 @@ token 用量在流式收尾的 `meta.usage` 中统计(`orchestration/streaming
## 路由策略(ROUTER_STRATEGY)
-`orchestration/strategy.py`:`ROUTER_STRATEGY=main_only`(默认)恒路由到主智能体;`llm_router` 为预留占位,当前实现同样回落 `MainOnlyStrategy`(safe-by-default)。实际的多智能体分流由 `@提及` + `call_subagent` 工具完成,见 [对话与智能体编排](chat.md)。
+`core/agent_runtime/strategy.py`:`ROUTER_STRATEGY=main_only`(默认)恒路由到主智能体;`llm_router` 为预留占位,当前实现同样回落 `MainOnlyStrategy`(safe-by-default)。实际的多智能体分流由 `@提及` + `call_subagent` 工具完成,见 [对话与智能体编排](chat.md)。
## 相关源码
| 主题 | 路径 |
|---|---|
-| 模型工厂 / JxOpenAIChatModel | `src/backend/core/llm/chat_models.py` |
-| 角色解析服务(DB + 缓存) | `src/backend/core/services/model_config.py` |
-| 角色定义 / 供应商仓储 | `src/backend/core/db/model_repository.py` |
+| 模型工厂 / JxOpenAIChatModel | `src/backend/core/models/chat_models.py` |
+| 角色解析服务(DB + 缓存) | `src/backend/core/models/config.py` |
+| 角色定义 / 供应商仓储 | `src/backend/core/models/repository.py` |
| 管理 API | `src/backend/api/routes/v1/models.py` |
-| 动态模型中间件 | `src/backend/core/llm/middlewares.py::DynamicModelMiddleware`,helper 在 `core/llm/hooks.py` |
-| MCP env 注入 | `src/backend/core/services/model_config.py::get_mcp_env_overlay`,`core/config/runtime_env.py` |
-| 个人 API-Key | `src/backend/api/routes/v1/api_keys.py`,`core/services/api_key_service.py`,`core/auth/backend.py` |
+| 动态模型中间件 | `src/backend/core/llm/runtime/`,helper 在 `core/harness/hooks.py` |
+| MCP env 注入 | `src/backend/core/models/config.py::get_mcp_env_overlay`,`core/config/runtime_env.py` |
+| 个人 API-Key | `src/backend/api/routes/v1/api_keys.py`,`core/auth/api_keys.py`,`core/auth/backend.py` |
| 用量日志 / 计费 | `src/backend/api/routes/v1/admin_usage_logs.py`,`api/routes/v1/admin_billing.py` |
-| 路由策略 | `src/backend/orchestration/strategy.py` |
+| 路由策略 | `src/backend/core/agent_runtime/strategy.py` |
| 主模型缺失快速失败 | `src/backend/api/routes/v1/chats/__init__.py::_ensure_main_model_configured` |
## 模型专属思考档位
diff --git a/document/zh-CN/modules/personal-agent.md b/document/zh-CN/modules/personal-agent.md
index d26c3ac1..b056ce30 100644
--- a/document/zh-CN/modules/personal-agent.md
+++ b/document/zh-CN/modules/personal-agent.md
@@ -219,24 +219,18 @@ Linux 桌面运行环境未提供输入驱动时,连接只包含会话管理
先按项目部署文档准备企业版、PostgreSQL、Redis 和 OpenSandbox 镜像。
-1. 构建专用电脑镜像:`docker build -f docker/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .`。不要覆盖正在运行的电脑使用的标签。
+1. 构建专用电脑镜像:`docker build -f docker/personal-agent/Dockerfile.personal-agent -t hugagent-personal-agent:v5 .`。不要覆盖正在运行的电脑使用的标签。
2. 设置 `HOST_STORAGE_PATH` 为宿主机绝对路径。
3. 为 `PERSONAL_AGENT_OPENSANDBOX_API_KEY` 设置至少 32 个字符的独立随机密钥。
4. 设置 `PERSONAL_AGENT_OPENSANDBOX_DOMAIN=personal-agent-opensandbox:8080`。
5. 设置 `PERSONAL_AGENT_IMAGE=hugagent-personal-agent:v5`。已有电脑在下次重建时使用新镜像。
-6. 使用根 Compose 文件和附加文件:`docker compose -f docker-compose.yml -f docker/compose.personal-agent.yml --profile personal-agent up -d`。
+6. 使用根 Compose 文件和附加文件:`docker compose -f docker-compose.yml -f docker/personal-agent/compose.personal-agent.yml --profile personal-agent up -d`。
7. 按项目现有迁移流程升级数据库。依次应用 `personalagent01` 至 `personalagent05`。这些迁移创建模块表、回填消息关联、建立独立任务会话,并增加跨设备会话操作表。
附加文件创建独立控制器、专用网络和私有配置卷。控制器需要访问 Docker socket。密钥不得提交到 Git。现有数据库密钥用于加密沙盒和邮箱凭据。
配置缺失时,Personal Agent 不能连接普通控制器作为替代。后台服务每分钟检查永久电脑。用户暂停任务队列时,电脑仍保留。
-## 验证范围
-
-自动化测试覆盖单一身份、并发任务、租约、审批、设备撤销、历史分页和普通聊天入口限制。隔离环境还验证了实际模型、真实云端截图和输入、文件持久化、SMTP/IMAP 附件和前后端流程。
-
-Ethereal 测试邮箱捕获邮件。它不能证明向普通互联网邮箱投递成功。Mac 和 Windows 原生桌面测试必须在对应系统完成。具体已通过项和待验项见内部验收记录。
-
回滚 personalagent02 前,必须等待所有消息回复完成。回滚把任务关联及独立指令归档到原消息元数据中。重新升级会恢复这些关联和指令。
## 状态栏
@@ -266,6 +260,8 @@ Personal Agent 用一个类型化工具 `pa_hugagent` 管理用户的 HugAgentOS
云端电脑内置轻量 `hugagent` CLI。CLI 管理现有后台。网页端和桌面端的云端模式共用 `cloud`。桌面端的本机模式使用 `device:`。
+云端电脑里这个客户端就叫 `hugagent`。在云电脑以外安装同一个客户端(`pip install` 后端源码,或 `scripts/build-hugagent-cli.py` 打出的 wheel)时,命令名是 `hugagent-session`,用法相同。工作站上的 `hugagent` 是终端客户端(`packages/cli`)。本机服务的 `onboard`、`serve`、`doctor`、`engine` 由 `hugagent-server` 提供,会话客户端收到这些命令时只提示改用 `hugagent-server`。
+
```bash
hugagent device list
hugagent --target cloud chat list
diff --git a/document/zh-CN/modules/projects-myspace.md b/document/zh-CN/modules/projects-myspace.md
index 3e42a848..d79a3b0e 100644
--- a/document/zh-CN/modules/projects-myspace.md
+++ b/document/zh-CN/modules/projects-myspace.md
@@ -35,7 +35,7 @@ teams ─────────┬── team_members(role: owner/admin/memb
## 项目(Projects)
-路由:`src/backend/api/routes/v1/projects.py`(CE 路由表),业务在 `core/services/project_service.py` 与 `project_file_service.py`。
+路由:`src/backend/api/routes/v1/projects.py`(CE 路由表),业务在 `core/projects/` 与 `project_file_service.py`。
| 方法 | 路径 | 说明 |
|---|---|---|
@@ -71,16 +71,16 @@ teams ─────────┬── team_members(role: owner/admin/memb
### 项目上下文如何进入对话
-在项目内发起对话时(请求携带 `project_id`),`core/services/session_operations/request_context.py` 组装 workflow context:
+在项目内发起对话时(请求携带 `project_id`),`core/chat/operations/request_context.py` 组装 workflow context:
1. 读取项目元信息——`project_name`、`project_instructions`、挂钩文件夹名和工作目录;项目指令仍读取 `AGENTS.md`,不递归枚举云端或本机项目文件;
2. `core/llm/factory/build.py` 把这些经 `_build_project_section` 注入 system prompt(`build_system_prompt(cfg, ctx=...)`);
3. 项目级记忆作用域随之生效:workspace 为 `project:`,团队项目的 mem0 桶为 `team:`(成员共享记忆),项目自身的 `metadata.memory_enabled` / `memory_write_enabled` 覆盖用户级开关(项目内缺省开启)——详见 [记忆系统](./memory.md);
-4. 沙箱侧路径作用域:项目对话中 agent 的 `/myspace/...` 文件操作被重定向到挂钩文件夹之下(`core/llm/tools/myspace_vfs/__init__.py` 的 `ProjectScope` 显式传参机制)。
+4. 沙箱侧路径作用域:项目对话中 agent 的 `/myspace/...` 文件操作被重定向到挂钩文件夹之下(`core/files/spaces/` 的 `ProjectScope` 显式传参机制)。
项目模式自定义提示词模板使用项目名称、目录范围和项目指令变量。升级已有部署前,须从已保存的模板中移除旧文件清单及文件总数占位符;运行时不再提供这些变量。
-团队项目权限沿用团队角色:owner/admin 恒为管理权限,member 按 `file_permission`(editor/viewer)二级控制(`core/auth/permissions_iface.py::require_project_access`)。
+团队项目权限沿用团队角色:owner/admin 恒为管理权限,member 按 `file_permission`(editor/viewer)二级控制(`core/auth/personal_permissions_iface.py::require_project_access`)。
### 项目侧栏交互
@@ -158,7 +158,7 @@ teams ─────────┬── team_members(role: owner/admin/memb
三条路径,互为补充:
-1. **附件注入(hooks)**:前端请求的 `attachments[]` 只携带 `file_id`、`name`、`mime_type`,不携带文件正文。`core/llm/hooks.py` 按 `file_id` 延迟下载、解析并缓存 `parsed_text` 与摘要;首轮只注入文件元数据和最多 5,000 字符的有界预览,xlsx 额外提供总行列数、表头和少量数据行。模型需要预览外的正文时通过 `read_artifact` 按需读取;图片仍走多模态注入。所有读取都校验 `user_id` 归属,防止伪造 `file_id` 跨用户访问;
+1. **附件注入(hooks)**:前端请求的 `attachments[]` 只携带 `file_id`、`name`、`mime_type`,不携带文件正文。`core/harness/hooks.py` 按 `file_id` 延迟下载、解析并缓存 `parsed_text` 与摘要;首轮只注入文件元数据和最多 5,000 字符的有界预览,xlsx 额外提供总行列数、表头和少量数据行。模型需要预览外的正文时通过 `read_artifact` 按需读取;图片仍走多模态注入。所有读取都校验 `user_id` 归属,防止伪造 `file_id` 跨用户访问;
2. **项目文件访问**:项目对话的 system prompt 提供项目指令和目录范围,不自动携带文件列表或文件总数。agent 按需使用目录、搜索和读取工具获取文件信息;
3. **沙箱虚拟文件系统**:开启代码执行时,`/myspace/...` 路径把我的空间映射进沙箱(懒加载 + 文件系统实时登记),团队项目映射团队文件夹——见 [沙箱](./sandbox.md)。
@@ -167,8 +167,8 @@ teams ─────────┬── team_members(role: owner/admin/memb
| 路径 | 职责 |
|---|---|
| `src/backend/api/routes/v1/projects.py` | 项目 API |
-| `src/backend/core/services/project_service.py` / `project_file_service.py` | 项目业务逻辑 |
-| `src/backend/core/services/project_scope.py` | `ProjectScope`(沙箱路径作用域) |
+| `src/backend/core/projects/` / `project_file_service.py` | 项目业务逻辑 |
+| `src/backend/core/projects/personal_scope.py` | `ProjectScope`(沙箱路径作用域) |
| `src/backend/api/routes/v1/myspace_folders.py` | 个人文件夹 API |
| `src/backend/api/routes/v1/artifacts.py` | 资产列表 / 会话收藏 / 加入知识库 |
| `src/backend/edition_ee/routes/team_files.py` | 团队文件夹与文件 API(商业版 EE) |
@@ -177,9 +177,9 @@ teams ─────────┬── team_members(role: owner/admin/memb
| `src/backend/core/db/models/identity.py` | `UserFolder` 等共享身份 ORM |
| `src/backend/edition_ee/db/models/identity.py` | `Team` / `TeamMember` / `TeamFolder` ORM(仅 EE) |
| `src/backend/core/db/models/artifact.py` | `Artifact` ORM |
-| `src/backend/core/llm/hooks.py` | 附件上下文注入(`_build_file_context` 等) |
+| `src/backend/core/harness/hooks.py` | 附件上下文注入(`_build_file_context` 等) |
| `src/backend/core/llm/factory/build.py` | 项目 section 注入 system prompt |
-| `src/backend/core/llm/tools/myspace_vfs/__init__.py` | 我的空间 ↔ 沙箱映射层 |
+| `src/backend/core/files/spaces/` | 我的空间 ↔ 沙箱映射层 |
| `src/frontend/src/components/projects/` | 项目前端组件 |
| `src/frontend/src/components/sidebar/Sidebar.tsx` | 侧栏项目分组、快捷新建与项目菜单 |
| `src/frontend/src/components/myspace/` | 我的空间前端组件 |
@@ -249,23 +249,20 @@ Config「团队管理」新增成员或调整成员权限时,可选「所有
缺失文件或失败下载也在下次正常同步时补齐。
升级前先停止本地写入并备份数据库及 myspace_cache。
-运行 `python src/backend/scripts/clean_myspace_duplicates.py --all` 预览;
-加 `--apply --backup-dir <新的备份目录>` 才执行。
-清理删除每个重名组的全部有效记录及镜像路径,不保留“最新一条”。
-采用删除标记避免历史记录回填复活,原对象和恢复日志保留在维护备份中。
+升级前检查同名记录,并制定经审核的数据清理与恢复方案。当前不提供独立去重脚本。
然后执行 Alembic 升级(EE: personalname01;CE: ce_0018),恢复服务并运行对账与端到端测试。
迁移遇到未清理重名会终止,不会自行删除其他环境数据。
恢复须先停止写入、撤销唯一索引,再由备份恢复;不要直接向正常服务恢复重名记录。
### 空间文件工具
-空间文件工具统一由 `core/llm/tools/space_tools.py` 注册,调用名为 `space_list_myspace_files`、`space_stage_myspace_file`、`space_create_folder`、`space_move`、`space_delete`;商业版另提供 `space_list_team_files`。通用 Read/Write/Edit/Glob/Grep 名称保持不变。
+空间文件工具统一由 `core/files/spaces/` 注册,调用名为 `space_list_myspace_files`、`space_stage_myspace_file`、`space_create_folder`、`space_move`、`space_delete`;商业版另提供 `space_list_team_files`。通用 Read/Write/Edit/Glob/Grep 名称保持不变。
收藏会话工具已移除;历史会话仍可通过 `list_related_chats` 和 `read_chat` 访问。
### 挂载目录统一同步
-个人空间监听实现位于 `core/space_sync/`,团队适配位于 `edition_ee/services/space_sync/`。监听回调只入内存队列,后台批量写入挂载目录之外的 SQLite 日志,再执行带权限、版本检查的数据库及对象存储更新。原生移动保留文件和文件夹 ID;空目录、删除、重启恢复及解析缓存失效使用同一同步流程。数据库提交也会刷新已注册的挂载目录,回滚不刷新。冲突保留本地内容并使同步屏障报告失败。网络下载、上传和人工审批时间不属于回调延迟,完整同步延迟须按实际环境测量。
+个人空间监听实现位于 `core/files/spaces/`,团队适配位于 `edition_ee/services/workspace_adapter.py`。监听回调只入内存队列,后台批量写入挂载目录之外的 SQLite 日志,再执行带权限、版本检查的数据库及对象存储更新。原生移动保留文件和文件夹 ID;空目录、删除、重启恢复及解析缓存失效使用同一同步流程。数据库提交也会刷新已注册的挂载目录,回滚不刷新。冲突保留本地内容并使同步屏障报告失败。网络下载、上传和人工审批时间不属于回调延迟,完整同步延迟须按实际环境测量。
### 项目环境
@@ -275,7 +272,7 @@ Config「团队管理」新增成员或调整成员权限时,可选「所有
后端按项目配置限制能力注册、显式调用、历史会话资源恢复、子智能体和插件动态加载。空的自定义环境不回退到全部资源。环境调整从下一轮对话生效;正在执行的响应及其派生作业保留原配置。项目外对话不受影响。基础文件与对话工具仍遵守原有权限规则。
-实现按职责划分:`core/project_environment/` 定义配置校验和纯依赖展开;资源服务读取当前用户获授权的目录;环境服务负责保存和预览;运行策略负责冻结及收窄注册、调用范围。前端编辑器只组织界面,`useProjectEnvironment` 负责资源加载、依赖预览和市场回填,资源选框负责展示显式绑定与推导依赖。
+实现按职责划分:`core/projects/environment/` 定义配置校验和纯依赖展开;资源服务读取当前用户获授权的目录;环境服务负责保存和预览;运行策略负责冻结及收窄注册、调用范围。前端编辑器只组织界面,`useProjectEnvironment` 负责资源加载、依赖预览和市场回填,资源选框负责展示显式绑定与推导依赖。
### 云文档在线编辑
diff --git a/document/zh-CN/modules/prompts.md b/document/zh-CN/modules/prompts.md
index 8c4d77f2..31e4a81c 100644
--- a/document/zh-CN/modules/prompts.md
+++ b/document/zh-CN/modules/prompts.md
@@ -29,7 +29,7 @@ src/backend/prompts/prompt_text/default/system/
└── 40_format.system.md # 输出格式 + [ref:工具名-N] 引用规范
```
-在 base prompt 之后,运行时还会按上下文追加动态段:工具与技能说明(`_TOOLS_AND_SKILLS_NOTICE`)、轻量知识库目录(`prompts/kb_lite_section.py`)、项目模式段(`prompts/project_section.py`,仅项目对话注入)、代码执行段与批量模式提示(由 `factory/build.py` 追加)、子智能体路由表(`core/llm/subagent_tool.py::build_subagent_prompt_section`)。
+在 base prompt 之后,运行时还会按上下文追加动态段:工具与技能说明(`_TOOLS_AND_SKILLS_NOTICE`)、轻量知识库目录(`prompts/kb_lite_section.py`)、项目模式段(`prompts/project_section.py`,仅项目对话注入)、代码执行段与批量模式提示(由 `factory/build.py` 追加)、子智能体路由表(`core/llm/subagents/tool.py::build_subagent_prompt_section`)。
### 缓存设计
@@ -48,7 +48,7 @@ src/backend/prompts/prompt_text/default/system/
### 执行 Manifest 与运行时绑定
-Agent 真正开始执行前,`core/llm/execution_manifest.py::PromptManifestBuilder` 会记录最终请求面。每个有序 PromptSection 都带 `id`、来源、信任级、优先级、缓存类别、预算、token 估算、内容哈希和版本;每个最终 ToolDefinition 同样对名称、完整描述、canonical 参数 schema、权限策略和恢复策略生成稳定哈希。工具定义按稳定 id 排序,提示词分段则保持刻意设计的前缀顺序。
+Agent 真正开始执行前,`core/harness/execution_manifest.py::PromptManifestBuilder` 会记录最终请求面。每个有序 PromptSection 都带 `id`、来源、信任级、优先级、缓存类别、预算、token 估算、内容哈希和版本;每个最终 ToolDefinition 同样对名称、完整描述、canonical 参数 schema、权限策略和恢复策略生成稳定哈希。工具定义按稳定 id 排序,提示词分段则保持刻意设计的前缀顺序。
Builder 最终产出 `prompt_hash`、`prompt_manifest_hash`、`tool_manifest_hash`、`context_hash` 和统一的 `aggregate_hash`。Manifest 构建与 AgentScope 模型请求共用同一份 run-scoped 工具/技能冻结快照,因此“记录的请求面”和“实际发送的请求面”不会错位;渐进式插件激活会让旧快照失效,并在下一次模型调用前发布明确递增的 `surface_generation`。
@@ -58,15 +58,15 @@ Runtime Binder 把这份脱敏 Manifest 挂到本轮递归不可变的 Asset Bun
### 统一 Context IR
-每次模型调用前,`core/llm/context_ir.py` 都会把最终上下文统一转换成不可变的 `ContextItem`。用户输入、助手历史、账户身份、冻结记忆、项目/文件材料、附件、工具调用与结果、压缩摘要、执行中追加指令、Harness 系统提醒,都会携带机器可读的 `kind`、来源、信任级、可见性、优先级、单项预算、截断策略、内容引用/哈希、缓存类别和创建顺序。为兼容模型,记忆和对话中途的提醒仍可渲染成 `user` role,但系统不再靠 XML 标签或 role 猜测它们是谁说的。
+每次模型调用前,`core/llm/context/ir.py` 都会把最终上下文统一转换成不可变的 `ContextItem`。用户输入、助手历史、账户身份、冻结记忆、项目/文件材料、附件、工具调用与结果、压缩摘要、执行中追加指令、Harness 系统提醒,都会携带机器可读的 `kind`、来源、信任级、可见性、优先级、单项预算、截断策略、内容引用/哈希、缓存类别和创建顺序。为兼容模型,记忆和对话中途的提醒仍可渲染成 `user` role,但系统不再靠 XML 标签或 role 猜测它们是谁说的。
`ContextAssembler` 使用确定性顺序,并设置明确的消息预算:模型窗口先预留 15% 给响应,再扣除最终工具 schema 的 canonical token 预算。关键系统规则和当前用户指令不会被低可信内容挤掉;可选内容按优先级选择;文本和工具输出按声明的头尾保留策略截断;工具调用与对应结果则作为一个整体保留、缩短或排除。脱敏 Context Manifest 会逐项说明“纳入、截断、排除”及原因,不保存正文;其哈希会进入本次请求的 `context_hash` 与 `aggregate_hash`,再重新绑定到该 run 的 Asset Bundle。
-`core/llm/context_adapter.py::AgentScopeContextAdapter` 是 Context IR 与 AgentScope 消息之间唯一的转换入口。持久化会话中的来源元数据可在历史回放时保留,最终模型 role 和 SSE 行为继续兼容。`ManifestBoundAgent` 会在 run-scoped 工具/技能请求面冻结后,通过公开的 `Agent.reply` 链路完成最终装配。
+`core/llm/context/adapter.py::AgentScopeContextAdapter` 是 Context IR 与 AgentScope 消息之间唯一的转换入口。持久化会话中的来源元数据可在历史回放时保留,最终模型 role 和 SSE 行为继续兼容。`ManifestBoundAgent` 会在 run-scoped 工具/技能请求面冻结后,通过公开的 `Agent.reply` 链路完成最终装配。
## 提示词版本池(prompt_versions)
-版本池把多套提示词存进单行 `ContentBlock(id="prompt_versions")`,payload 结构 `{active: {kind: version_id}, versions: [...]}`,服务层为 `core/services/prompt_version_service.py`:
+版本池把多套提示词存进单行 `ContentBlock(id="prompt_versions")`,payload 结构 `{active: {kind: version_id}, versions: [...]}`,服务层为 `core/llm/prompt_versions.py`:
- **六类 kind**(`VALID_KINDS`):`system`(主智能体)、`code_exec`(代码执行能力段)、`distillation`(技能蒸馏)、`plan_mode`(计划模式)、`subagents`(平台默认探索员 / 执行员 / 审查员的独立提示词)、`turbo`(极速模式独立提示词)。
- 每个版本含 `(kind, id, name, description, parts[])`,part 即 `{part_id, display_name, content, sort_order, is_enabled}`。
@@ -94,8 +94,8 @@ Runtime Binder 把这份脱敏 Manifest 挂到本轮递归不可变的 Asset Bun
| kind | 运行时消费方 | 解析顺序 |
|---|---|---|
| `code_exec` | `factory/build.py` 在 `CODE_CAPABILITY_ENABLED=true` 时把该段(代码能力提示词)拼到 system prompt 尾部;单一真源为 `prompt_version_service.render_code_capability_segment()`,管理台 preview 同源 | DB 激活版本 → `prompts/prompt_text/code_exec/system/*.system.md` |
-| `distillation` | 技能蒸馏(`core/llm/skill_distiller.py`,把对话轨迹蒸馏为可复用技能) | DB 激活版本 → `prompts/prompt_text/distillation/skill_distiller.system.md` |
-| `plan_mode` | 计划模式生成子智能体(`orchestration/subagents/plan_mode.py::_load_plan_prompt`) | DB `plan_mode` 激活版本 → 旧版 system 版本的 `system/90_plan_mode` 分段 → `prompts/prompt_text/plan_mode/plan_mode.system.md` → 硬编码兜底 |
+| `distillation` | 技能蒸馏(`features/distillation/`,把对话轨迹蒸馏为可复用技能) | DB 激活版本 → `prompts/prompt_text/distillation/skill_distiller.system.md` |
+| `plan_mode` | 计划模式生成子智能体(`orchestration/plans/`) | DB `plan_mode` 激活版本 → 旧版 system 版本的 `system/90_plan_mode` 分段 → `prompts/prompt_text/plan_mode/plan_mode.system.md` → 硬编码兜底 |
| `turbo` | 极速模式(对话模式选「极速」时,`factory/build.py` 以该提示词**整体替换**主系统提示词)。工具集由 Config「系统配置 → 极速模式」动态配置(`turbo.mcp_server_ids`,默认联网搜索/网页抓取/知识库检索三件套,**独立于能力目录的启停**);用户显式呼唤的技能 / @子智能体 / 插件可临时入场(`turbo.manual_invoke_enable`);迭代上限可配(`turbo.max_iters`,默认 4)。单一真源为 `prompt_version_service.render_turbo_system_prompt()` | DB `turbo` 激活版本 → `prompts/prompt_text/turbo/turbo.system.md` → 硬编码兜底 |
子智能体不走版本池整版拼装:`prompt_runtime.py::build_subagent_system_prompt()` 以用户自定义 `system_prompt` 为核心,复用激活版本(或文件)的 `20_tools_policy` / `65_citations` / `60_format` 分段组装,详见 [对话与智能体编排](chat.md)。
@@ -120,18 +120,6 @@ Runtime Binder 把这份脱敏 Manifest 挂到本轮递归不可变的 Asset Bun
两端点接受 `ADMIN_TOKEN` 或 `CONFIG_TOKEN`。快照 schema 经 `PROMPT_BLOCK_MAP` 校验,docs 快照与 prompts 快照不能从错误的端点互导。
-### 脚本(src/backend/scripts/)
-
-```bash
-# 导出(走运行中的后端 API;也支持 --database-url 直连库)
-python scripts/export_content.py --api-url http://localhost:3000/api --only prompts
-# → scripts/exported/prompts_snapshot_.json
-
-# 导入到目标环境(用目标机 .env 的 ADMIN_TOKEN)
-python scripts/import_content.py --api-url http:///api --prompts prompts_snapshot_.json
-# 支持 --no-overwrite / --dry-run
-```
-
离线生产环境(镜像包交付,DB 卷持久化)同样适用:快照文件随镜像包拷入,在 backend 容器内 `curl -X POST .../v1/content/prompts/import` 导入即可,无需重启。换品牌环境迁移时,需人工通读快照逐处改写品牌相关话术后再导入(不要机械查找替换)。
## 相关源码
@@ -141,10 +129,9 @@ python scripts/import_content.py --api-url http:///api --prompts prompts_s
| 运行时装配 + 缓存 | `src/backend/prompts/prompt_runtime.py` |
| Provider(filesystem/inline/minimal) | `src/backend/prompts/provider.py` |
| 配置(provider/parts/PROMPT_DIR) | `src/backend/prompts/prompt_config.py`,`prompts/config/default.json` |
-| 版本池服务 | `src/backend/core/services/prompt_version_service.py` |
+| 版本池服务 | `src/backend/core/llm/prompt_versions.py` |
| 管理台路由 | `src/backend/api/routes/v1/admin_prompts.py` |
-| 迁移接口(export/import) | `src/backend/api/routes/v1/content.py`,`core/content/content_blocks.py` |
-| 迁移脚本 | `src/backend/scripts/export_content.py`,`scripts/import_content.py` |
+| 迁移接口(export/import) | `src/backend/api/routes/v1/content.py`,`core/files/content/content_blocks.py` |
| 系统提示词兜底文件 | `src/backend/prompts/prompt_text/default/system/` |
| 场景提示词兜底 | `src/backend/prompts/prompt_text/{code_exec,distillation,plan_mode,turbo}/` |
| 动态段 | `src/backend/prompts/kb_lite_section.py`,`prompts/project_section.py` |
diff --git a/document/zh-CN/modules/sandbox.md b/document/zh-CN/modules/sandbox.md
index 3421f464..d2db66e6 100644
--- a/document/zh-CN/modules/sandbox.md
+++ b/document/zh-CN/modules/sandbox.md
@@ -63,11 +63,11 @@ Cube 的设计取舍(远端节点版的代价):所有语言统一走"写
框架保留最多 64 个进程记录,仅回收已完成记录;新增输出有界,省略内容通过 `output_omitted_chars` 明示。本地及 script_runner 的完整输出存于当前会话工作区的 `.__process_*/stdout.log` 和 `stderr.log`,通过 `output_files` 返回真实路径;单流超过 64 MiB 时停止命令,已写日志仍可读。OpenSandbox 后台日志合并 stdout/stderr;Cube 总输出超过 64 MiB 时停止。运行期间定期调用沙箱保活:OpenSandbox 可续租,Cube 的兼容服务可能不支持延长服务端 TTL,返回 `lifetime_note` 说明这一边界。普通会话的命令账本保存任务状态与有界输出。OpenSandbox 使用原执行标识和日志游标恢复;不重跑命令。其他 provider 正常退出时停止进程;异常重启后标记 unknown。显式删除会话会停止任务。团队项目在命令终态返回后同步源码;必须等待完成,不能把 running 当成已经保存。
-后台执行由服务端采集状态、保存日志并保持沙箱活动。模型可以执行独立工作;没有其他工作时,模型结束当前运行。完成事件在下一次推理前进入活动会话,或使用现有会话运行入口唤醒空闲模型。write_stdin 用于按需读取新增输出、短时等待和取消,不用于反复轮询。认证日志 API 的读取不消耗模型输出游标。聊天界面不展示独立的后台命令栏。页面预先订阅 /v1/commands/events 长连接,后端在唤醒运行就绪后立即推送运行标识,页面随即接入现有输出流,不等待任务轮询。重连时补发已就绪的运行;已结束的短回复从历史加载。实现边界见 [后台 Bash ADR](../../internal/adr/20261018-background-bash-completion.md)。
+后台执行由服务端采集状态、保存日志并保持沙箱活动。模型可以执行独立工作;没有其他工作时,模型结束当前运行。完成事件在下一次推理前进入活动会话,或使用现有会话运行入口唤醒空闲模型。write_stdin 用于按需读取新增输出、短时等待和取消,不用于反复轮询。认证日志 API 的读取不消耗模型输出游标。聊天界面不展示独立的后台命令栏。页面预先订阅 /v1/commands/events 长连接,后端在唤醒运行就绪后立即推送运行标识,页面随即接入现有输出流,不等待任务轮询。重连时补发已就绪的运行;已结束的短回复从历史加载。
沙箱会话标识由 `resolve_sandbox_session(sandbox_session_id, chat_id)` 解析:非空显式 ID 优先,未传或传空值时统一回落到 `chat_id`。主智能体、计划执行、批量项和所有子智能体因此使用同一个会话沙箱;子智能体结束时不会销毁它。
-**MySpace 实时登记**:写在 `/myspace` 下的文件**就是**用户「我的空间」里的文件,两侧任何时刻看到的都应是同一份状态。登记由**文件系统本身**驱动(`core/myspace/watcher.py`):镜像目录 `myspace_cache/{uid}/` 里发生任何写入或删除,都被登记回 artifact 账本,**不区分是谁写的**。
+**MySpace 实时登记**:写在 `/myspace` 下的文件**就是**用户「我的空间」里的文件,两侧任何时刻看到的都应是同一份状态。登记由**文件系统本身**驱动(`core/files/spaces/`):镜像目录 `myspace_cache/{uid}/` 里发生任何写入或删除,都被登记回 artifact 账本,**不区分是谁写的**。
早先的做法是让会写文件的工具各自登记(write / edit / 文件增删改 / `Bash` 前后拍目录快照)。这套写法的前提是"改动只可能从这几个口子进来",而这个前提不成立:`nohup` 起的后台进程在命令返回之后才写文件,子智能体和批量任务在另一条协程里写,技能里的 CLI 直接写,MCP 服务端也写。每多一个入口就要多补一处登记,漏掉的那一处的表现是——文件躺在用户网盘的磁盘上,界面上看不见也删不掉,而每个新建沙箱都会把这份目录挂进来,成了"上个会话的残留中间文件"。
@@ -84,13 +84,11 @@ Cube 的设计取舍(远端节点版的代价):所有语言统一走"写
其它要点:
-- **登记不挂会话、也不挂产物卡片**:「我的空间」是用户级的一份目录,每个会话的沙箱都挂着它,文件事件里只有路径、没有会话身份——谁写的,这里无从得知。所以登记只记到用户名下(`Artifact.chat_id` 为空),产物区的文件卡片则完全不由登记器挂:卡片只能由**写文件的那一轮自己**挂(模型调 `pin_to_workspace`,或那一轮里的 Write/交付兜底,都走 `core.llm.workspace` 的 ContextVar),那里"哪个会话、哪个文件"都是确定的。曾经拿"这个用户当下随便哪个在跑的会话"顶上,结果是另一个会话写的文件被记到了这个会话名下,还在它的产物区里多出一张卡片。
+- **登记不挂会话、也不挂产物卡片**:「我的空间」是用户级的一份目录,每个会话的沙箱都挂着它,文件事件里只有路径、没有会话身份——谁写的,这里无从得知。所以登记只记到用户名下(`Artifact.chat_id` 为空),产物区的文件卡片则完全不由登记器挂:卡片只能由**写文件的那一轮自己**挂(模型调 `pin_to_workspace`,或那一轮里的 Write/交付兜底,都走 `core.llm.runtime.workspace` 的 ContextVar),那里"哪个会话、哪个文件"都是确定的。曾经拿"这个用户当下随便哪个在跑的会话"顶上,结果是另一个会话写的文件被记到了这个会话名下,还在它的产物区里多出一张卡片。
- **判定基准是 artifact 记录,不是镜像缓存**:开了 bind mount 后沙箱的 `/workspace/myspace/{uid}` 就是后端 `myspace_cache/{uid}` 本身,"沙箱文件与镜像缓存一致"恒真、不能再当作"已登记"。这条拓扑差异曾让回写恒为空转。
- **"磁盘比账本新"是唯一依据**:后端自己往镜像目录写文件(界面上传的物化、否决后的还原)时会把 mtime 对齐到登记时间,于是监听器不会把后端自己的写入当成沙箱的新改动转头再登记一次——不必另外维护一张"这是我自己写的"的表。
- **每个 worker 都监听,认领去重**:确认条活在跑这个会话的那个进程里,所以监听器必须每个 worker 都跑(代价是 inotify watch 按「worker 数 × 目录数」占内核配额 `fs.inotify.max_user_watches`,目录极多的部署要相应调大,超限会**静默丢事件**);同一次改动由 `core.infra.ephemeral` 的认领保证只处理一次,且优先交给托管着该用户会话的进程(它才弹得出确认条)。
-- **沙箱目录不在本机时**(`script_runner` / `cube`:会话工作区在容器内、或整个沙箱在远端)由 `core/myspace/sandbox_sync.py` 在命令结束后把沙箱现状搬进镜像目录,之后的判定与登记完全同路。
-
-历史欠账(本次改造上线前积压的未登记文件 / 该删没删的残留)是**一次性数据迁移**,用 `scripts/reconcile_myspace_mirror.py` 手工跑一次即可(`--dry-run` 先看规模,残留副本的清理加 `--prune-stale`)。它不做成每次启动都执行的扫描:那既是白跑,也会掩盖实时这条路上真正的漏洞。
+- **沙箱目录不在本机时**(`script_runner` / `cube`:会话工作区在容器内、或整个沙箱在远端)由 `core/files/spaces/sandbox.py` 在命令结束后把沙箱现状搬进镜像目录,之后的判定与登记完全同路。
### 大文件产物交付
@@ -175,8 +173,8 @@ artifact 送入沙盒、`Bash` 执行后 `/myspace` 写回同步;backend 与 s
## 管理员沙箱管理
-- **只读监控(安全管理台)**:`api/routes/v1/config_security.py` 暴露 `/v1/config/security/sandbox/*`——总览、实例列表、单实例详情、快照列表、重建历史、生效配置;底层走 provider 的 `admin_*` 接口,按 `admin_capabilities()` 声明裁剪 UI(script_runner 无法枚举实例即置灰对应列)。
-- **依赖重建(商业版 EE)**:`api/routes/v1/admin_sandbox.py`(`/v1/admin/sandbox/*`)聚合所有技能声明的 pip/apt 依赖(`core/services/skill_deps_aggregator.py`),管理员一键触发沙箱镜像重建:`script-runner` / `opensandbox` 目标走本地 `docker compose build`,`cube` 目标走 SSH 到远端节点重建模板并热切(`core/services/sandbox_rebuild_service.py` + `cube_template_builder`),可查看每次 run 的状态与日志。技能新依赖从此"烤入"镜像,无需手工改 Dockerfile。
+- **只读监控(安全管理台)**:`features/security_console/api/config_security.py` 暴露 `/v1/config/security/sandbox/*`——总览、实例列表、单实例详情、快照列表、重建历史、生效配置;底层走 provider 的 `admin_*` 接口,按 `admin_capabilities()` 声明裁剪 UI(script_runner 无法枚举实例即置灰对应列)。
+- **依赖重建(商业版 EE)**:`features/sandbox_ops/api/admin_sandbox.py`(`/v1/admin/sandbox/*`)聚合所有技能声明的 pip/apt 依赖(`core/capabilities/skills/deps/aggregator.py`),管理员一键触发沙箱镜像重建:`script-runner` / `opensandbox` 目标走本地 `docker compose build`,`cube` 目标走 SSH 到远端节点重建模板并热切(`features/sandbox_ops/rebuild.py` + `cube_template_builder`),可查看每次 run 的状态与日志。技能新依赖从此"烤入"镜像,无需手工改 Dockerfile。
## 关键环境变量
@@ -210,17 +208,17 @@ artifact 送入沙盒、`Bash` 执行后 `/myspace` 写回同步;backend 与 s
| `src/backend/core/sandbox/factory.py` | provider 显式选择;初始化错误直接上报 |
| `src/backend/core/sandbox/providers/script_runner/provider.py` | 轻量沙箱(CE) |
| `src/backend/core/sandbox/providers/opensandbox/provider.py` | OpenSandbox 共享 provider 主体 |
-| `src/backend/core/sandbox/providers/opensandbox/session.py` | 会话/快照/park-restore worker(EE) |
+| `src/backend/core/sandbox/providers/opensandbox/` | 会话/快照/park-restore worker(EE) |
| `src/backend/core/sandbox/providers/opensandbox/execution.py` | 执行路径 + idle reaper(EE) |
-| `src/backend/core/sandbox/providers/opensandbox/internals.py` | volume 构造、metadata、user pool(EE) |
+| `src/backend/core/sandbox/providers/opensandbox/` | volume 构造、metadata、user pool(EE) |
| `src/backend/core/sandbox/providers/opensandbox/pool.py` | 双桶预热池 |
| `src/backend/core/sandbox/providers/cube/provider.py` | Cube 远端 MicroVM provider(EE) |
| `src/backend/core/llm/tools/sandbox_tool.py` | Bash / write_stdin / sandbox_put_artifact / sandbox_get_artifact |
-| `src/backend/core/llm/offloader.py` | 超长结果落盘 /workspace/.offload |
-| `src/backend/api/routes/v1/admin_sandbox.py` | 依赖重建管理 API(EE) |
-| `src/backend/api/routes/v1/config_security.py` | 安全管理台沙箱只读视图 |
-| `src/backend/core/services/sandbox_rebuild_service.py` | 镜像/模板重建编排(EE) |
-| `docker/Dockerfile.script-runner` / `docker/Dockerfile.opensandbox` / `docker/Dockerfile.cube-sandbox` | 三种沙箱镜像 |
+| `src/backend/core/llm/context/offloader.py` | 超长结果落盘 /workspace/.offload |
+| `src/backend/features/sandbox_ops/api/admin_sandbox.py` | 依赖重建管理 API(EE) |
+| `src/backend/features/security_console/api/config_security.py` | 安全管理台沙箱只读视图 |
+| `src/backend/features/sandbox_ops/rebuild.py` | 镜像/模板重建编排(EE) |
+| `docker/sandbox/Dockerfile.script-runner` / `docker/sandbox/Dockerfile.opensandbox` / `docker/sandbox/Dockerfile.cube-sandbox` | 三种沙箱镜像 |
相关文档:[技能系统](agent-skills.md) · [MCP 工具系统](mcp-tools.md) · [个人空间](projects-myspace.md) · [版本与许可](../editions/overview.md)
@@ -245,3 +243,24 @@ OpenSandbox 的实例列表与实例计数读取控制器当前运行清单,
控制器查询失败时,计数不可用;不能把查询失败报告成零实例。
连接池诊断保留 scope=worker 与 worker_pid,表示当前后端进程的本地池,
不代表全部进程的用户配额。该修复不把本地池上限改成跨进程全局配额。
+
+## 共享容量与维护所有权
+
+OPENSANDBOX_POOL_MAX_TOTAL 现在由控制器依据 Docker 实例库存统一执行,
+覆盖所有 backend worker、预热实例、会话恢复和计算机实例。各进程池统计仅用于本地诊断。
+控制器在容器创建时持有共享数据卷上的文件锁,不把挂载初始化放入临界区;
+库存达到上限或另一创建正在占用准入锁时返回 429,调用方可稍后重试。
+控制器的 opensandbox_data 数据卷应在同一控制器的进程间共享。
+
+领导进程从共享会话登记发现空闲会话,即使会话由其他 worker 创建或经历过重启,
+也进入快照回收流程。失效登记按身份清理,不阻断其他会话。
+快照任务随领导权停止;GC 只删除本次处理的 snapshot_id 对应记录。
+
+输出读取不再触发挂载准备或对象存储目录写入。目录初始化发生在创建路径,
+执行和文件访问仍核验权限。挂载探测超时或返回不确定错误时保留现有 FUSE helper,
+只有确认缺失或断开时重建。命令观察失败先有限重试,再交给持久恢复;
+没有可恢复引用的进程须确认停止,不能仅因观察失败就丢弃控制句柄。
+
+此机制需要同时更新 backend 和 OpenSandbox 控制器镜像;只更新 backend 无法获得全局容量约束。
+
+技能挂载必须配置 HOST_STORAGE_PATH,指向与后端 STORAGE_PATH 对应的宿主存储目录。缺失配置会明确失败,不再回退到源码目录。重启接管只识别显式归属标签,不根据旧启动命令推断归属。
diff --git a/document/zh-CN/modules/sites.md b/document/zh-CN/modules/sites.md
index 90d351e6..185fb08a 100644
--- a/document/zh-CN/modules/sites.md
+++ b/document/zh-CN/modules/sites.md
@@ -66,13 +66,13 @@
| 环节 | 位置 |
|---|---|
| 发布与查询工具 | `src/backend/extensions/mcp_servers/site_publish_mcp/`(`publish_site` / `list_sites`,由「站点」插件提供) |
-| 内部接口 | `src/backend/api/routes/v1/internal_sites.py`(`/v1/internal/sites/publish`、`/v1/internal/sites/list`) |
-| 站点查询服务 | `src/backend/core/services/site_listing.py`(本机与云端统一记录格式) |
-| 业务服务 | `src/backend/core/services/site_service.py` |
-| 公开托管路由 | `src/backend/api/routes/sites_serve.py`(`GET /site/{slug}/{path}`) |
-| 管理接口 | `src/backend/api/routes/v1/sites.py`(`/v1/sites`) |
-| 访问密码 | `src/backend/core/services/site_password.py`(哈希 + 凭据)、`src/backend/api/routes/site_gate.py`(统一验证页) |
-| 数据表 | `sites`(`core/db/models/site.py`) |
+| 内部接口 | `src/backend/features/sites/api/internal_sites.py`(`/v1/internal/sites/publish`、`/v1/internal/sites/list`) |
+| 站点查询服务 | `src/backend/features/sites/listing.py`(本机与云端统一记录格式) |
+| 业务服务 | `src/backend/features/sites/management.py` |
+| 公开托管路由 | `src/backend/features/sites/api/sites_serve.py`(`GET /site/{slug}/{path}`) |
+| 管理接口 | `src/backend/features/sites/api/sites.py`(`/v1/sites`) |
+| 访问密码 | `src/backend/features/sites/`(哈希 + 凭据)、`src/backend/features/sites/api/site_gate.py`(统一验证页) |
+| 数据表 | `sites`(`features/sites/models.py`) |
| 前端管理面板 | `src/frontend/src/components/sites/SitesPanel.tsx`(实验室 → 站点)、`SitePasswordField.tsx`(密码管理) |
| nginx 转发 | `src/frontend/default.conf.template` 的 `location /site/` |
diff --git a/document/zh-CN/modules/storage.md b/document/zh-CN/modules/storage.md
index d34be521..0adcf949 100644
--- a/document/zh-CN/modules/storage.md
+++ b/document/zh-CN/modules/storage.md
@@ -4,7 +4,7 @@ CE 和 EE 的云端 Docker 部署默认使用 S3 兼容存储。桌面离线本
设置 STORAGE_TYPE=s3,并配置 S3_ENDPOINT、S3_BUCKET、S3_REGION、S3_ACCESS_KEY 和 S3_SECRET_KEY。后端和沙盒控制器均须能够访问该服务地址。
-可选的 docker/compose.object-storage.yml 启动独立 S3 服务。它使用独立数据卷,不发布宿主端口,也不共用 Milvus 存储。
+可选的 docker/object-storage/compose.object-storage.yml 启动独立 S3 服务。它使用独立数据卷,不发布宿主端口,也不共用 Milvus 存储。
在新的 CE checkout 中运行 python3 scripts/configure-ce-docker.py。初始化工具生成独立随机访问密钥和秘密密钥,并拒绝覆盖已有 .env。
@@ -12,7 +12,7 @@ CE 和 EE 的云端 Docker 部署默认使用 S3 兼容存储。桌面离线本
已有部署切换存储前,先备份配置、数据库和文件引用。复制所需对象并逐对象校验 SHA-256。保留源对象。
-存储工厂位于 core/storage/factory.py。S3StorageBackend 使用 boto3 并提供目录操作。LocalStorageBackend 服务桌面本地文件。OSS 和 OBS 适配仍属于 EE。
+存储工厂位于 core/files/storage/factory.py。S3StorageBackend 使用 boto3 并提供目录操作。LocalStorageBackend 服务桌面本地文件。OSS 和 OBS 适配仍属于 EE。
生成物仓库支持 S3、OSS 和 OBS。元数据记录保存在本地,并镜像到对象存储。迁移时保留原存储键,以保持历史下载引用有效。
diff --git a/document/zh-CN/modules/workflow-jobs.md b/document/zh-CN/modules/workflow-jobs.md
index 64a41ab8..53fb7f1b 100644
--- a/document/zh-CN/modules/workflow-jobs.md
+++ b/document/zh-CN/modules/workflow-jobs.md
@@ -23,11 +23,3 @@ run_job 将脚本交给沙箱的受管理进程接口执行。提交时先准备
派生实例采用主智能体的轮数策略,不套用轻量执行器或子智能体的固定轮数上限。完整任务只执行一次;输出格式错误、执行失败或回调结果不确定时,不自动重跑已可能产生副作用的任务。检查已有操作后再显式续跑。
Windows 本机作业使用 `.job/<完整 attempt ID>/`,减少一层目录,避免随包 Python 在深层数据目录导入作业 SDK 时超过传统路径长度限制。每次执行仍保留完整的唯一 attempt ID;续跑和取消使用已保存的实际目录及精确进程引用。
-
-原生桌面端到端验收脚本为 `src/backend/tests/integration/job_agent_local_smoke.py`。使用随包私有 Python 和新建隔离输出目录运行;验收真实 HTTP、作业进程、并发派生主智能体、子智能体、Bash 和文件结果。仅模型决策由本地脚本服务控制,不使用真实账号或数据,不替代安装包和 WebView2 窗口验收。
-
-## 验证
-
-相关单元与回归测试位于 src/backend/tests/orchestration/test_job*.py。显式真实沙箱测试位于 src/backend/tests/integration/job_managed_smoke.py,需指定现有作业作为沙箱上下文。它创建合成作业和临时回调端口,验证并发台账、线程退出、精确取消、跨进程接管、续跑和启动失败,不调用模型,不重新执行原始业务脚本。
-
-部署前须确认容器真正挂载的源码目录。工作区改动与运行容器的源码可能来自不同 checkout;仅重启错误挂载的容器不能使修改生效。
diff --git a/examples/customer-chat/.env.example b/examples/customer-chat/.env.example
deleted file mode 100644
index 0c63ba12..00000000
--- a/examples/customer-chat/.env.example
+++ /dev/null
@@ -1,6 +0,0 @@
-# Optional. Leave empty for offline host preview.
-# Backend must be an origin. Do not append /api.
-HUGAGENT_BACKEND_URL=http://127.0.0.1:3001
-HUGAGENT_DEV_PORT=4310
-# Change only when the backend uses another session cookie name.
-HUGAGENT_SESSION_COOKIE=jx_session
diff --git a/examples/customer-chat/.gitignore b/examples/customer-chat/.gitignore
deleted file mode 100644
index dbec1f16..00000000
--- a/examples/customer-chat/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-node_modules/
-dist/
-standalone-dist/
-*.tgz
-web-dist/
-.env
-.env.local
-vendor/
diff --git a/examples/customer-chat/README.md b/examples/customer-chat/README.md
deleted file mode 100644
index 058c830a..00000000
--- a/examples/customer-chat/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Customer chat example
-
-Two independent consumers are included: a scoped host UI package and a standalone browser application.
-Both import only the published SDK. The current file dependency is a monorepo convenience; replace it with a pinned SDK tarball/version when copying this directory to a customer repository.
-
-```sh
-npm install /path/to/hugagent-chat-sdk-1.0.0.tgz
-npm run build
-npm pack
-```
-
-Install the resulting customer package in src/frontend and build the platform with HUGAGENT_CHAT_CUSTOMIZATION=hugagent-customer-chat-example.
-For this repository's source example:
-
-```sh
-cd src/frontend
-HUGAGENT_CHAT_CUSTOMIZATION=../../examples/customer-chat/src/customization.tsx npm run build
-```
-
-Unset that variable to build the official default interface.
-A customer package must use the same SDK major and React major as the host. The host deduplicates React and the SDK.
-
-The example wraps default welcome/composer/message components. Customer source, CSS and lockfile live independently.
-For a replacement component, use useChatRuntime for draft, messages, attachments and actions.
-Preserve children for rich platform controls that the replacement does not implement.
-
-`npm run standalone` produces standalone-dist, served at /customer/ by the acceptance environment.
-Set VITE_CUSTOMER_API_BASE (default /api) and VITE_CUSTOMER_LOGIN_URL (default /login?redirect=/session.html) when building for your platform.
-The default login link opens the platform's normal login page. It does not depend on mock SSO.
-Use a same-origin API gateway and an authenticated platform session. Do not embed shared API keys in a public build.
-The standalone example renders plain-text replies, paginated history, attachment upload, cancellation, replay, and tool/question gates.
-It intentionally does not implement the entire platform's rich artifact, citation, plan and ontology UI; use default host children for those features.
-
-Customer UI images and bundles must be deployed separately from backend images. Pin both versions and test the desired combination before upgrade.
-The Docker example's customer source is inside the build context; external customer repositories require their own trusted build-stage COPY/install steps.
-
-For the complete no-source developer kit, run node scripts/export-customer-devkit.mjs from the repository root. See document/zh-CN/development/customer-devkit.md. The kit provides npm ci, npm run dev, offline slot preview, backend login, fixed API proxy, API debug and npm run test:package. Component builds bundle imported helpers, CSS and assets.
diff --git a/examples/customer-chat/debug.html b/examples/customer-chat/debug.html
deleted file mode 100644
index 04157231..00000000
--- a/examples/customer-chat/debug.html
+++ /dev/null
@@ -1 +0,0 @@
-API debug Host preview · Live chat · API debug API debug Sign in to backend Sign out Checking session
Requests use the fixed backend configured by your developer. Open browser Network tools to inspect SSE and uploads. No request body or cookies are logged by this kit.
GET endpoint/v1/me /v1/chats?page=1&page_size=10 /ready Send GET
\ No newline at end of file
diff --git a/examples/customer-chat/index.html b/examples/customer-chat/index.html
deleted file mode 100644
index db66376c..00000000
--- a/examples/customer-chat/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-Independent Customer Chat
-Host preview · Live chat · API debug Independent Customer Chat Sign in
-Ready
-
-
-Cancel run Resume
-New chat Attach
-
diff --git a/examples/customer-chat/package-lock.json b/examples/customer-chat/package-lock.json
deleted file mode 100644
index 4735d4f8..00000000
--- a/examples/customer-chat/package-lock.json
+++ /dev/null
@@ -1,1224 +0,0 @@
-{
- "name": "hugagent-customer-chat-example",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "hugagent-customer-chat-example",
- "version": "1.0.0",
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0"
- },
- "devDependencies": {
- "@hugagent/chat-sdk": "file:../../packages/chat-sdk",
- "@types/react": "^19.2.7",
- "@types/react-dom": "^19.2.0",
- "typescript": "~5.9.3",
- "vite": "^7.3.1"
- },
- "peerDependencies": {
- "@hugagent/chat-sdk": "^1.0.0"
- }
- },
- "../../packages/chat-sdk": {
- "name": "@hugagent/chat-sdk",
- "version": "1.0.0",
- "dev": true,
- "license": "UNLICENSED",
- "devDependencies": {
- "@types/react": "^19.2.7",
- "react": "^19.2.0",
- "typescript": "~5.9.3"
- },
- "peerDependencies": {
- "react": ">=19 <20"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- }
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
- "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
- "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
- "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
- "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
- "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
- "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
- "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
- "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
- "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
- "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
- "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
- "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
- "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
- "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
- "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
- "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
- "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
- "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
- "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
- "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
- "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
- "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
- "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
- "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
- "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
- "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@hugagent/chat-sdk": {
- "resolved": "../../packages/chat-sdk",
- "link": true
- },
- "node_modules/@napi-rs/lzma-linux-x64-gnu": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
- "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^22.20 || ^24.12 || >=25"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.7.tgz",
- "integrity": "sha512-GFF2tHk0NqYVObrK0+/ps/LEXsV/v2q6Lyiy7mZr+Ej24fpQScvqK2ziz9NHeEQYnKWKym38Ml7dZbPtC9tuAw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.7.tgz",
- "integrity": "sha512-BOymcXJFYYXqluDidj6sLelNjXruC3u1BqVxLn2OPCXH9xaPSOTH+MS4uychsGI1NU7tJoiv9KvqijDHFe+hhQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.7.tgz",
- "integrity": "sha512-jgVpGweIbls2H5jfBB70tnX52abMd5pJr3xmtoDILx6JdmGl+Un+tMb9R+MbHPVB46tTBpY8emtn7M71SnQhEQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.7.tgz",
- "integrity": "sha512-AVMpt94mrXH7WjyWKmd9nT4RC5yM0AN3Lt6A89xVIsuLDi3dDX6dxMytLbW36FlppY9JsoqUoS3LUUpUrYxiMQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.7.tgz",
- "integrity": "sha512-Nje29c/MyajxRNFGU05oyeTDS6SWdMg/wWMSlZSBTBju5BKz2NVc0kFiFSQGzRELNiB+CBk/ZvAVsWsENgjGag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.7.tgz",
- "integrity": "sha512-xxgN5/kMzCNz/NYJ4t49B0/WS6InNlWv6xvgwUQqNnWM9WJMMwMUGWrAxdNEgYWdHL0sMnuh8I87StGrIMTA+w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.7.tgz",
- "integrity": "sha512-5U/9gVn/X657I8+NHoHO9Wnfyd0lEXwqbslobRoB9TC8SwYzOitcIr6TLE7531Q/NWs3L4mb+YOGzlf3vHIeYw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.7.tgz",
- "integrity": "sha512-hcDS0a/Y8ggM3dcOfzH7UpSaogCViWTtNDSl0EXpWIj8g8/r86ITqptjil2hayTXS0Umzb+Yt3mjgpQVFVf2+A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.7.tgz",
- "integrity": "sha512-awbAr4N7Qd/yjZAQBEEPzgcPveYG2Gi5L6gq1/Bp+3SMRjn5F68Wv1zKsntzvvoioBR6yKvhLtWqu11am1TsxA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.7.tgz",
- "integrity": "sha512-Ut7xQJfeLhzu2yN3rHzVvL9ccyPJvN4aCdP1V7hJ5lGxX/eCzgB9SiMk/Md3J+rWWZgvoYsDW4BUFggJr32pBA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.7.tgz",
- "integrity": "sha512-trqoni/vHkuM5oo5iQhoKPh6fjjD7E80pMgCXWgGxU8vSRPUlwvG8GeYvEEcB6eJAxNa99WH9iVHPHelgtKiHA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.7.tgz",
- "integrity": "sha512-gvlUy0XFIwtUWiQmhc+4GT2Knm3KosinpUVhr7CtTFA78gFDnE5NWwMPzHrJsYpKkh2LlY7yvBIboN1FAV6Xeg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.7.tgz",
- "integrity": "sha512-8yS/kSXTJ/47Z62JW+C2AJ3vd/8zZw016vFjtNpZwmZlwJumLua4s1i78dkYuSSqQ+0aIMxJcBp/4uIwp2ehjA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.7.tgz",
- "integrity": "sha512-mxVSKvvty0hOP1AFo/l4Kpv3i3HK0zx4rYUrirZDToYlWhS3xzmIkq3vUJ7rtrM2cPyXveDyDT6M1FfXZ9hTRA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.7.tgz",
- "integrity": "sha512-lEse1zra1CagM+uHH2bl28GInDsZZ45NJzHei2OMaXQFzZWKBO1hFo07rS0xB6ayV4WU360UrSmPDSyCMpYsUQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.7.tgz",
- "integrity": "sha512-d/cqCoiG9WMZ5VuJZ5Qdlv9g97QyoGwnmvHccvGhKSacTy7xFrAS+jMcJSBdMNOw1oacSkl6p7K6O/DRYTKL3g==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.7.tgz",
- "integrity": "sha512-kVEA//CeDOZZP1XCH2JBgqXb9+ALlwuA/4civaazMVdgzaSQC9WM38w65zG/LLk0lA7F9Od19CSBZW7J9mCbNg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.7.tgz",
- "integrity": "sha512-14oLfDsOSr0TSntT/34jRg/V8B2XW/Jomh9zX9CjIM9gwwNlI1OCymS/GVn0DV32h67eRf7U0GGem0rvfXgNwQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.7.tgz",
- "integrity": "sha512-dFVtPfjZqAnUPpPY4Bfg+87yo1IBW9HiY8iCFtg49Pn46MfZTebSHZh8PL6SOsgyt9LQP8Nj/sE6jm3aWpySmQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.7.tgz",
- "integrity": "sha512-Dm9CZ2SM3u2NgvBj4awW/gGqkOwzH8f6MSYIJE6+bKcKLI+T0+LhpD6vtetTfrIIx+rLXDf5RJqBP6v7V5O+wg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.7.tgz",
- "integrity": "sha512-oaxsvS2mL6+78741CT2HCORBvy1USg3Wqpt5qvZwy3aMXRMl4zxyDo8maeFRWJkF3dp0Wbi71fKH5Gr9YI3vYA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.7.tgz",
- "integrity": "sha512-UKZz1Y4mxUqkWh60uH7B1aZn6UcAimPk4ipRktpb3wfZq7JrzZYyAXTwiafkb3T+kd5P3Sf+vzcRdsDks4WtjQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.7.tgz",
- "integrity": "sha512-WWkSuC1J+mZJnsDh0mRaCAFSWfQdl6y3fDjZeWElI6zHo8CcOSG5lY3yurKJjEq+FrX0WD60sFrVVpbHqzMmrw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.7.tgz",
- "integrity": "sha512-u9bp2yXCI3/BUjUxY6uWknAY32pfvvDCPysyUTySotx0dj3ydlFHrRQYfn/b/kZgnfjyEwySMtI7Ga054uiysQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.7.tgz",
- "integrity": "sha512-EhX6N3XPMEnVU5cRfNgzPOKcSucsbu3nNNWuoAaEuKIqmddBKHN5b9d4rPe65T1I+FaOT176JKiZNsUBp3rqbA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "19.3.0",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
- "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.3.0",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
- "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.3.0"
- }
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/esbuild": {
- "version": "0.28.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
- "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.2",
- "@esbuild/android-arm": "0.28.2",
- "@esbuild/android-arm64": "0.28.2",
- "@esbuild/android-x64": "0.28.2",
- "@esbuild/darwin-arm64": "0.28.2",
- "@esbuild/darwin-x64": "0.28.2",
- "@esbuild/freebsd-arm64": "0.28.2",
- "@esbuild/freebsd-x64": "0.28.2",
- "@esbuild/linux-arm": "0.28.2",
- "@esbuild/linux-arm64": "0.28.2",
- "@esbuild/linux-ia32": "0.28.2",
- "@esbuild/linux-loong64": "0.28.2",
- "@esbuild/linux-mips64el": "0.28.2",
- "@esbuild/linux-ppc64": "0.28.2",
- "@esbuild/linux-riscv64": "0.28.2",
- "@esbuild/linux-s390x": "0.28.2",
- "@esbuild/linux-x64": "0.28.2",
- "@esbuild/netbsd-arm64": "0.28.2",
- "@esbuild/netbsd-x64": "0.28.2",
- "@esbuild/openbsd-arm64": "0.28.2",
- "@esbuild/openbsd-x64": "0.28.2",
- "@esbuild/openharmony-arm64": "0.28.2",
- "@esbuild/sunos-x64": "0.28.2",
- "@esbuild/win32-arm64": "0.28.2",
- "@esbuild/win32-ia32": "0.28.2",
- "@esbuild/win32-x64": "0.28.2"
- }
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.20",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
- "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
- "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.30",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
- "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.20",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/react": {
- "version": "19.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz",
- "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz",
- "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.28.0"
- },
- "peerDependencies": {
- "react": "^19.3.1"
- }
- },
- "node_modules/rollup": {
- "version": "4.64.7",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.7.tgz",
- "integrity": "sha512-aCiJONoZp0lNGJ6sYqspLkh6UXaWEey/7Ko2TZyXQ4kgzAhDUxAgbt/oulXKmD+zFHXw1k+RF6ZZWOrjp7DZnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.9"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
- "@rollup/rollup-android-arm-eabi": "4.64.7",
- "@rollup/rollup-android-arm64": "4.64.7",
- "@rollup/rollup-darwin-arm64": "4.64.7",
- "@rollup/rollup-darwin-x64": "4.64.7",
- "@rollup/rollup-freebsd-arm64": "4.64.7",
- "@rollup/rollup-freebsd-x64": "4.64.7",
- "@rollup/rollup-linux-arm-gnueabihf": "4.64.7",
- "@rollup/rollup-linux-arm-musleabihf": "4.64.7",
- "@rollup/rollup-linux-arm64-gnu": "4.64.7",
- "@rollup/rollup-linux-arm64-musl": "4.64.7",
- "@rollup/rollup-linux-loong64-gnu": "4.64.7",
- "@rollup/rollup-linux-loong64-musl": "4.64.7",
- "@rollup/rollup-linux-ppc64-gnu": "4.64.7",
- "@rollup/rollup-linux-ppc64-musl": "4.64.7",
- "@rollup/rollup-linux-riscv64-gnu": "4.64.7",
- "@rollup/rollup-linux-riscv64-musl": "4.64.7",
- "@rollup/rollup-linux-s390x-gnu": "4.64.7",
- "@rollup/rollup-linux-x64-gnu": "4.64.7",
- "@rollup/rollup-linux-x64-musl": "4.64.7",
- "@rollup/rollup-openbsd-x64": "4.64.7",
- "@rollup/rollup-openharmony-arm64": "4.64.7",
- "@rollup/rollup-win32-arm64-msvc": "4.64.7",
- "@rollup/rollup-win32-ia32-msvc": "4.64.7",
- "@rollup/rollup-win32-x64-gnu": "4.64.7",
- "@rollup/rollup-win32-x64-msvc": "4.64.7",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/scheduler": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
- "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
- "license": "MIT"
- },
- "node_modules/source-map-js": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
- "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/vite": {
- "version": "7.3.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz",
- "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.27.0 || ^0.28.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- }
- }
-}
diff --git a/examples/customer-chat/package.json b/examples/customer-chat/package.json
deleted file mode 100644
index 0838c053..00000000
--- a/examples/customer-chat/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "hugagent-customer-chat-example",
- "version": "1.0.0",
- "private": true,
- "type": "module",
- "scripts": {
- "build": "tsc --noEmit && node scripts/build-component.mjs && tsc --emitDeclarationOnly",
- "standalone": "vite build --base /customer/ --outDir standalone-dist",
- "dev": "vite --open /preview.html",
- "dev:headless": "vite",
- "build:web": "vite build",
- "preview:web": "vite preview",
- "prepack": "npm run build",
- "test": "node scripts/test-proxy.mjs",
- "test:package": "node scripts/test-package.mjs"
- },
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0"
- },
- "devDependencies": {
- "@types/react": "^19.2.7",
- "typescript": "~5.9.3",
- "vite": "^7.3.1",
- "@types/react-dom": "^19.2.0",
- "@hugagent/chat-sdk": "file:../../packages/chat-sdk"
- },
- "exports": {
- ".": {
- "types": "./dist/customization.d.ts",
- "import": "./dist/customization.js"
- }
- },
- "files": [
- "dist",
- "README.md"
- ],
- "peerDependencies": {
- "@hugagent/chat-sdk": "^1.0.0"
- }
-}
diff --git a/examples/customer-chat/preview.html b/examples/customer-chat/preview.html
deleted file mode 100644
index dea4dac0..00000000
--- a/examples/customer-chat/preview.html
+++ /dev/null
@@ -1 +0,0 @@
-Customer host preview Host preview · Live chat · API debug Customer host preview This offline host tests the public slot contract. Use Live chat for backend tests.
\ No newline at end of file
diff --git a/examples/customer-chat/scripts/build-component.mjs b/examples/customer-chat/scripts/build-component.mjs
deleted file mode 100644
index 8e93c500..00000000
--- a/examples/customer-chat/scripts/build-component.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-import { readFileSync, writeFileSync, existsSync } from 'node:fs';
-import { build } from 'vite';
-await build({
- configFile: false,
- build: {
- outDir: 'dist', emptyOutDir: true, sourcemap: false,
- lib: { entry: 'src/customization.tsx', formats: ['es'], fileName: 'customization', cssFileName: 'theme' },
- rollupOptions: { external: id => id === 'react' || id.startsWith('react/') ||
- id === 'react-dom' || id.startsWith('react-dom/') || id === '@hugagent/chat-sdk' || id.startsWith('@hugagent/chat-sdk/') },
- },
-});
-
-// Vite extracts library CSS. Keep the package entry self-contained for the host.
-if (existsSync('dist/theme.css')) writeFileSync('dist/customization.js', "import './theme.css';\n" + readFileSync('dist/customization.js', 'utf8'));
diff --git a/examples/customer-chat/scripts/developer-proxy.mjs b/examples/customer-chat/scripts/developer-proxy.mjs
deleted file mode 100644
index a4ced863..00000000
--- a/examples/customer-chat/scripts/developer-proxy.mjs
+++ /dev/null
@@ -1,43 +0,0 @@
-import { createHash } from 'node:crypto';
-
-/** A fixed upstream gateway; no client-supplied destination is accepted. */
-export function createDeveloperProxy(env) {
- const raw = env.HUGAGENT_BACKEND_URL;
- if (!raw) return {};
- const target = new URL(raw);
- if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password ||
- target.search || target.hash || target.pathname !== '/') {
- throw new Error('HUGAGENT_BACKEND_URL must be an HTTP(S) origin, without a path, credentials, query or fragment.');
- }
- const upstreamCookie = env.HUGAGENT_SESSION_COOKIE || 'jx_session';
- if (!/^[A-Za-z0-9_-]+$/.test(upstreamCookie)) throw new Error('Invalid session cookie name');
- const localCookie = 'hugagent_dev_' + createHash('sha256').update(target.origin).digest('hex').slice(0, 16);
- const configure = proxy => {
- proxy.on('proxyReq', (outgoing, incoming) => {
- // Cookies are scoped to this upstream; never forward unrelated localhost cookies.
- const session = (incoming.headers.cookie || '').split(';').map(v => v.trim()).find(v => v.startsWith(localCookie + '='));
- outgoing.removeHeader('cookie');
- outgoing.removeHeader('authorization');
- if (session) outgoing.setHeader('cookie', upstreamCookie + '=' + session.slice(localCookie.length + 1));
- outgoing.setHeader('x-forwarded-host', incoming.headers.host || 'localhost');
- outgoing.setHeader('x-forwarded-proto', 'http');
- });
- proxy.on('proxyRes', response => {
- // Keep Secure, HttpOnly, SameSite and expiry. Only rewrite the development cookie name/domain.
- response.headers['set-cookie'] = (response.headers['set-cookie'] || [])
- .filter(cookie => cookie.startsWith(upstreamCookie + '='))
- .map(cookie => {
- const rewritten = cookie.replace(upstreamCookie + '=', localCookie + '=').replace(/;\s*Domain=[^;]*/gi, '');
- return /;\s*HttpOnly(?:;|$)/i.test(rewritten) ? rewritten : rewritten + '; HttpOnly';
- });
- delete response.headers['access-control-allow-origin'];
- delete response.headers['access-control-allow-credentials'];
- });
- };
- const options = { target: target.origin, changeOrigin: true, secure: true, xfwd: false, configure,
- proxyTimeout: 180000, timeout: 180000 };
- return {
- '^/api(?:/|$)': { ...options, rewrite: path => path.replace(/^\/api(?=\/|$)/, '') || '/' },
- '^/login(?:[?]|$)': options,
- };
-}
diff --git a/examples/customer-chat/scripts/test-package.mjs b/examples/customer-chat/scripts/test-package.mjs
deleted file mode 100644
index 1bc62ada..00000000
--- a/examples/customer-chat/scripts/test-package.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import { execFileSync } from 'node:child_process';
-import { mkdtempSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import { join, resolve } from 'node:path';
-import assert from 'node:assert/strict';
-const pkg = JSON.parse(readFileSync('package.json'));
-const sdkFile = pkg.devDependencies['@hugagent/chat-sdk'].replace(/^file:/, '');
-if (!sdkFile.endsWith('.tgz')) throw new Error('Run this check in the exported developer kit');
-const component = resolve(pkg.name.replaceAll('@', '').replaceAll('/', '-') + '-' + pkg.version + '.tgz');
-if (!existsSync(component)) throw new Error('Run npm pack first');
-const host = mkdtempSync(join(tmpdir(), 'hugagent-customer-host-'));
-const react = JSON.parse(readFileSync('node_modules/react/package.json')).version;
-const vite = JSON.parse(readFileSync('node_modules/vite/package.json')).version;
-writeFileSync(join(host, 'package.json'), JSON.stringify({ private: true, type: 'module', dependencies: {
- '@hugagent/chat-sdk': 'file:' + resolve(sdkFile), [pkg.name]: 'file:' + component, react, vite,
-} }, null, 2));
-execFileSync('npm', ['install', '--prefer-offline', '--ignore-scripts', '--no-audit', '--no-fund'], { cwd: host, stdio: 'pipe' });
-const installed = JSON.parse(readFileSync(join(host, 'node_modules', pkg.name, 'package.json')));
-assert.equal(installed.dependencies?.['@hugagent/chat-sdk'], undefined);
-assert.ok(installed.peerDependencies['@hugagent/chat-sdk']);
-writeFileSync(join(host, 'entry.js'), "export { default } from " + JSON.stringify(pkg.name) + ";");
-writeFileSync(join(host, 'build.mjs'), "import{build}from'vite';await build({configFile:false,logLevel:'error',build:{lib:{entry:'entry.js',formats:['es'],fileName:'customer',cssFileName:'customer'}}});");
-execFileSync(process.execPath, ['build.mjs'], { cwd: host, stdio: 'pipe' });
-assert.ok(readFileSync(join(host, 'dist/customer.js'), 'utf8').includes('Customer Portal'));
-assert.ok(existsSync(join(host, 'dist/customer.css')));
-if (process.env.HUGAGENT_EXPECT_HELPER) {
- assert.ok(readFileSync(join(host, 'dist/customer.js'), 'utf8').includes('Customer helper included'));
- assert.ok(readFileSync(join(host, 'dist/customer.css'), 'utf8').includes('customer-extra-badge'));
-}
-console.log('PASS customer tarball installs and builds in a fresh host: ' + host);
diff --git a/examples/customer-chat/scripts/test-proxy.mjs b/examples/customer-chat/scripts/test-proxy.mjs
deleted file mode 100644
index 54e2a9f1..00000000
--- a/examples/customer-chat/scripts/test-proxy.mjs
+++ /dev/null
@@ -1,26 +0,0 @@
-import assert from 'node:assert/strict';
-import { EventEmitter } from 'node:events';
-import { createDeveloperProxy } from './developer-proxy.mjs';
-for (const url of ['file:///tmp', 'http://user:password@host', 'https://host/api', 'https://host/?target=other']) {
- assert.throws(() => createDeveloperProxy({ HUGAGENT_BACKEND_URL: url }));
-}
-assert.deepEqual(createDeveloperProxy({}), {});
-const a = createDeveloperProxy({ HUGAGENT_BACKEND_URL: 'https://backend.example' });
-const b = createDeveloperProxy({ HUGAGENT_BACKEND_URL: 'https://other.example' });
-function cookie(proxyConfig) {
- const proxy = new EventEmitter(); proxyConfig['^/api(?:/|$)'].configure(proxy);
- const response = { headers: { 'set-cookie': ['jx_session=secret; Domain=backend.example; Secure; SameSite=Lax', 'unrelated=deny'] } };
- proxy.emit('proxyRes', response);
- const cookies = response.headers['set-cookie'];
- assert.equal(cookies.length, 1); assert.ok(!cookies[0].includes('Domain='));
- assert.ok(cookies[0].includes('; HttpOnly')); assert.ok(cookies[0].includes('Secure; SameSite=Lax'));
- return { proxy, value: cookies[0].split(';')[0] };
-}
-const first = cookie(a), second = cookie(b); assert.notEqual(first.value, second.value);
-const headers = new Map([['authorization', 'secret'], ['cookie', 'unrelated=x']]);
-const request = { removeHeader: name => headers.delete(name), setHeader: (name,value) => headers.set(name,value) };
-first.proxy.emit('proxyReq', request, { headers: { host: 'localhost:4310', cookie: 'unrelated=x; ' + second.value } });
-assert.equal(headers.has('cookie'), false); assert.equal(headers.has('authorization'), false);
-first.proxy.emit('proxyReq', request, { headers: { host: 'localhost:4310', cookie: 'unrelated=x; ' + first.value } });
-assert.equal(headers.get('cookie'), 'jx_session=secret');
-console.log('PASS fixed target validation, Secure/HttpOnly retention, cross-target cookie isolation and authorization filtering');
diff --git a/examples/customer-chat/session.html b/examples/customer-chat/session.html
deleted file mode 100644
index d62e4fb2..00000000
--- a/examples/customer-chat/session.html
+++ /dev/null
@@ -1 +0,0 @@
-Session callback Completing login
Retry login
\ No newline at end of file
diff --git a/examples/customer-chat/src/controls.ts b/examples/customer-chat/src/controls.ts
deleted file mode 100644
index c2ab7600..00000000
--- a/examples/customer-chat/src/controls.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { ChatClient, type ChatEvent, type QuestionAnswer } from '@hugagent/chat-sdk';
-
-/** Deliberately uses DOM textContent: server/model text must never become executable HTML. */
-export class Controls {
- private readonly pending = new Map();
- constructor(private readonly client: ChatClient, private readonly root: HTMLElement,
- private readonly chat: () => string, private readonly error: (message: string) => void) {}
- clear() { this.pending.clear(); this.root.replaceChildren(); }
- private remove(id: string) { this.pending.get(id)?.remove(); this.pending.delete(id); }
- private panel(id: string, title: string) {
- if (this.pending.has(id)) return null;
- const panel = document.createElement('section'); panel.dataset.request = id;
- const heading = document.createElement('p'); heading.textContent = title; panel.append(heading);
- this.pending.set(id, panel); this.root.append(panel); return panel;
- }
- private button(panel: HTMLElement, label: string, action: () => Promise) {
- const button = document.createElement('button'); button.type = 'button'; button.textContent = label;
- button.addEventListener('click', () => {
- const buttons = [...panel.querySelectorAll('button')]; buttons.forEach(item => { item.disabled = true; });
- void action().catch(error => {
- this.error(String(error)); buttons.forEach(item => { item.disabled = false; });
- });
- }); panel.append(button);
- }
- consume(event: ChatEvent) {
- if (event.type === 'file_confirm' || event.type === 'design_pick') {
- const id = String(event.confirm_id || ''); if (!id) return;
- if (event.expired || event.resolved) return this.remove(id);
- const panel = this.panel(id, String(event.message || event.question || event.logical_path || event.op || 'Confirm operation'));
- if (!panel) return;
- const decide = async (decision: 'allow' | 'deny' | 'choice' | 'skip', optionId?: string) => {
- const result = await this.client.confirmFile(this.chat(), id, decision, optionId);
- if (!result.ok && !result.stale) throw new Error(result.message || 'Decision was not accepted');
- this.remove(id);
- if (result.stale) this.error(result.message || 'This confirmation has expired');
- };
- if (event.type === 'design_pick') {
- for (const option of Array.isArray(event.options) ? event.options as Record[] : []) {
- this.button(panel, String(option.title || option.id), () => decide('choice', String(option.id)));
- }
- this.button(panel, 'Skip', () => decide('skip'));
- } else {
- this.button(panel, 'Allow', () => decide('allow')); this.button(panel, 'Deny', () => decide('deny'));
- }
- }
- if (event.type === 'user_question_resolved') this.remove(String(event.request_id || ''));
- if (event.type !== 'user_question') return;
- const id = String(event.request_id || ''); if (!id) return;
- const panel = this.panel(id, 'Assistant needs your answer'); if (!panel) return;
- const answers: (() => QuestionAnswer)[] = [];
- for (const question of Array.isArray(event.questions) ? event.questions as Record[] : []) {
- const fieldset = document.createElement('fieldset'), legend = document.createElement('legend');
- legend.textContent = String(question.question || ''); fieldset.append(legend);
- const selections: HTMLInputElement[] = [];
- for (const option of Array.isArray(question.options) ? question.options as Record[] : []) {
- const label = document.createElement('label'), input = document.createElement('input');
- input.type = question.multi_select ? 'checkbox' : 'radio'; input.name = id + ':' + String(question.id);
- input.value = String(option.id); label.append(input, document.createTextNode(String(option.label))); fieldset.append(label); selections.push(input);
- }
- const custom = document.createElement('input'); custom.setAttribute('aria-label', 'Custom answer'); fieldset.append(custom);
- panel.append(fieldset);
- answers.push(() => ({ id: String(question.id), selected: selections.filter(item => item.checked).map(item => item.value), custom: custom.value }));
- }
- this.button(panel, 'Submit answers', async () => {
- const result = await this.client.answerQuestion(this.chat(), id, answers.map(read => read()));
- if (!result.ok && !result.stale) throw new Error(result.message || 'Answer was not accepted');
- this.remove(id); // A successful POST atomically claims and ends the pending request.
- if (result.stale) this.error(result.message || 'This question has expired');
- });
- }
- async recover() {
- const [confirms, questions] = await Promise.all([this.client.pendingConfirms(this.chat()), this.client.pendingQuestions(this.chat())]);
- for (const event of confirms.pendings || []) this.consume({ ...event, type: event.kind === 'design_pick' ? 'design_pick' : 'file_confirm' });
- for (const event of questions.requests || []) this.consume({ ...event, type: 'user_question' });
- }
-}
diff --git a/examples/customer-chat/src/css.d.ts b/examples/customer-chat/src/css.d.ts
deleted file mode 100644
index 35306c6f..00000000
--- a/examples/customer-chat/src/css.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module '*.css';
diff --git a/examples/customer-chat/src/customization.tsx b/examples/customer-chat/src/customization.tsx
deleted file mode 100644
index 3be05a17..00000000
--- a/examples/customer-chat/src/customization.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { defineChatCustomization, useChatRuntime, type ChatSlotProps, type MessageSlotProps } from '@hugagent/chat-sdk/react';
-import './theme.css';
-
-function Welcome({ children }: ChatSlotProps) {
- const { view, actions } = useChatRuntime();
- return
- Customer Portal {view.productName}
- actions.setDraft('Please summarize this document.')}>Use a template
-
- {children}
- ;
-}
-function Message({ message, children }: MessageSlotProps) {
- return
- {message.role === 'assistant' ? 'Portal Assistant' : 'You'}
- {children}
-
;
-}
-function Composer({ children }: ChatSlotProps) {
- const { view } = useChatRuntime();
- return
- {view.conversation.sending ? 'Working' : 'Ready'}
- {children}
- ;
-}
-export default defineChatCustomization({
- apiVersion: 1, id: 'customer-portal',
- slots: { welcome: Welcome, message: Message, composer: Composer },
-});
diff --git a/examples/customer-chat/src/debug.ts b/examples/customer-chat/src/debug.ts
deleted file mode 100644
index a1cb48b2..00000000
--- a/examples/customer-chat/src/debug.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import './login.js';
-import './developer.css';
-const status = document.getElementById('session')!;
-const result = document.getElementById('result')!;
-async function session() {
- try {
- const response = await fetch('/api/v1/me', { credentials: 'same-origin' });
- if (response.status === 401) { status.textContent = 'Signed out'; return; }
- const body = await response.json();
- status.textContent = response.ok && body.code >= 10000 && body.code < 20000 && body.data?.user_id
- ? 'Signed in' : 'Backend error: ' + response.status;
- } catch { status.textContent = 'Backend unavailable. Configure HUGAGENT_BACKEND_URL and restart npm run dev.'; }
-}
-document.getElementById('request')!.addEventListener('click', () => {
- void (async () => {
- try {
- const path = (document.getElementById('endpoint') as HTMLSelectElement).value;
- const response = await fetch('/api' + path, { credentials: 'same-origin' });
- result.textContent = 'HTTP ' + response.status + '\n' + await response.text();
- } catch (error) { result.textContent = String(error); }
- })();
-});
-document.getElementById('logout')!.addEventListener('click', () => {
- void (async () => {
- try {
- const response = await fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'same-origin' });
- if (!response.ok) throw new Error('Logout failed: ' + response.status);
- await session();
- } catch (error) { status.textContent = String(error); }
- })();
-});
-void session();
diff --git a/examples/customer-chat/src/developer.css b/examples/customer-chat/src/developer.css
deleted file mode 100644
index afec40f6..00000000
--- a/examples/customer-chat/src/developer.css
+++ /dev/null
@@ -1,3 +0,0 @@
-body{font-family:system-ui,sans-serif;max-width:1100px;margin:24px auto;padding:16px;color:#172b4d;background:#f5f8fc}nav{display:flex;gap:12px;flex-wrap:wrap}button,input,textarea,select{font:inherit;margin:6px;padding:8px}textarea{display:block;width:90%;min-height:90px}button{cursor:pointer}button:disabled{cursor:default}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:white;padding:16px}.preview-layout{display:grid;grid-template-columns:220px minmax(0,1fr);gap:24px}aside button{display:block}article,section.customer-composer{padding:12px;background:white;border-radius:10px;margin:10px 0}@media(max-width:650px){.preview-layout{grid-template-columns:1fr}body{margin:0}}
-
-*{box-sizing:border-box}[data-chat-customization] .preview-layout .customer-welcome{height:auto}[data-chat-customization]{--color-primary:#1554ad;--color-text:#172b4d;--color-text-secondary:#526581;--color-bg-container:white;--color-border:#c9d5e6;--radius-sm:6px}.preview-layout>section{min-width:0}
diff --git a/examples/customer-chat/src/login.ts b/examples/customer-chat/src/login.ts
deleted file mode 100644
index 5f03566d..00000000
--- a/examples/customer-chat/src/login.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-// Bind a login callback to the tab that started it; this value is not a session credential.
-for (const link of document.querySelectorAll('a[data-login], a#login')) {
- link.addEventListener('click', event => {
- event.preventDefault();
- const state = crypto.randomUUID();
- sessionStorage.setItem('hugagent-login', JSON.stringify({ state, started: Date.now() }));
- const redirect = '/session.html?state=' + encodeURIComponent(state);
- location.assign('/login?redirect=' + encodeURIComponent(redirect));
- });
-}
-export {};
diff --git a/examples/customer-chat/src/preview.tsx b/examples/customer-chat/src/preview.tsx
deleted file mode 100644
index 5f946843..00000000
--- a/examples/customer-chat/src/preview.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createRoot } from 'react-dom/client';
-import { PreviewHost } from '@hugagent/chat-sdk/preview';
-import customization from './customization.js';
-import './developer.css';
-createRoot(document.getElementById('root')!).render( );
diff --git a/examples/customer-chat/src/session.ts b/examples/customer-chat/src/session.ts
deleted file mode 100644
index 0df0bc11..00000000
--- a/examples/customer-chat/src/session.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-// Remove one-time credentials before any API call or further navigation.
-const url = new URL(location.href);
-const state = url.searchParams.get('state');
-const started = sessionStorage.getItem('hugagent-login');
-sessionStorage.removeItem('hugagent-login');
-const code = url.searchParams.get('code') || url.searchParams.get('ticket');
-history.replaceState(null, '', '/session.html');
-void (async () => {
- try {
- const expected = started ? JSON.parse(started) : null;
- if (!state || expected?.state !== state || Date.now() - expected.started > 300000) throw new Error('Login state missing or expired. Start login from API debug.');
- if (!code) throw new Error('No login credential. Use the login link.');
- const response = await fetch('/api/v1/auth/ticket/exchange', { method: 'POST', credentials: 'same-origin',
- headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) });
- const body = await response.json();
- if (!response.ok || body.code < 10000 || body.code >= 20000) throw new Error(body.message || 'Login exchange failed');
- location.replace('/debug.html');
- } catch (error) { document.getElementById('session')!.textContent = String(error); }
-})();
-export {};
diff --git a/examples/customer-chat/src/standalone.css b/examples/customer-chat/src/standalone.css
deleted file mode 100644
index f61c6e4d..00000000
--- a/examples/customer-chat/src/standalone.css
+++ /dev/null
@@ -1,6 +0,0 @@
-/* Independent consumer styles; no host selectors or assets. */
-:root { color-scheme: light dark; font-family: system-ui, sans-serif; }
-main { max-width: 840px; margin: 32px auto; padding: 16px; }
-textarea { display: block; width: 100%; min-height: 100px; box-sizing: border-box; }
-button { padding: 8px 16px; margin: 8px 8px 8px 0; }
-[data-role="assistant"] { border-inline-start: 3px solid currentColor; padding-inline-start: 16px; white-space: pre-wrap; }
diff --git a/examples/customer-chat/src/standalone.ts b/examples/customer-chat/src/standalone.ts
deleted file mode 100644
index 346e522c..00000000
--- a/examples/customer-chat/src/standalone.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import './login.js';
-import { Controls } from './controls.js';
-import { ChatClient, type ChatEvent, type ChatHistoryMessage } from '@hugagent/chat-sdk';
-import './standalone.css';
-
-// Serve this independent application behind the platform's same-origin API gateway.
-const client = new ChatClient({ baseUrl: import.meta.env.VITE_CUSTOMER_API_BASE || '/api' });
-const element = (id: string) => document.getElementById(id) as T;
-const status = element('status');
-element('login').href = import.meta.env.VITE_CUSTOMER_LOGIN_URL || '/login?redirect=/session.html';
-const input = element('message');
-const send = element('send');
-const cancel = element('cancel');
-const resume = element('resume');
-const messages = element('messages');
-const controls = new Controls(client, element('controls'), () => chatId, text => { status.textContent = text; });
-let chatId = new URL(location.href).searchParams.get('chat') || '';
-let runId = '', offset = 0, assistant: HTMLElement | null = null;
-let running = false;
-let attachment: { file_id: string; name: string; mime_type: string; download_url: string } | null = null;
-let controller: AbortController | undefined;
-function row(role: string, content: string) {
- const item = document.createElement('p'); item.dataset.role = role; item.textContent = content;
- messages.append(item); return item;
-}
-function busy(value: boolean) {
- running = value; send.disabled = value; cancel.disabled = !value || !runId;
- element('new').disabled = value;
- element('file').disabled = value;
- resume.disabled = value || !runId;
-}
-function rememberChat(id: string) {
- chatId = id; const url = new URL(location.href); url.searchParams.set('chat', id); history.replaceState(null, '', url);
-}
-async function historyMessages() {
- if (!chatId) return;
- const historyChatId = chatId;
- const history = { items: [] as ChatHistoryMessage[] };
- for (let page = 1; page <= 10000; page++) {
- const batch = await client.messages(historyChatId, page, 100);
- history.items.push(...batch.items);
- if (batch.items.length < 100) break;
- if (page === 10000) throw new Error('History limit reached');
- }
- if (chatId !== historyChatId) return;
- messages.replaceChildren();
- [...history.items].sort((a: ChatHistoryMessage, b: ChatHistoryMessage) => (a.chat_seq || 0) - (b.chat_seq || 0))
- .forEach(item => row(item.role, item.content));
-}
-function consume(event: ChatEvent) {
- controls.consume(event);
- if (event.type === 'run_snapshot' && event.state && typeof event.state === 'object') {
- const snapshot = event.state as Record;
- const blocks = Array.isArray(snapshot.blocks) ? snapshot.blocks as Record[] : [];
- let content = '';
- for (const block of blocks) {
- if (block.kind === 'answer') content = String(block.content || '');
- else if (block.kind === 'text') content += String(block.content || '');
- }
- assistant ??= messages.lastElementChild?.getAttribute('data-role') === 'assistant'
- ? messages.lastElementChild as HTMLElement : row('assistant', '');
- assistant.textContent = content;
- const signals = snapshot.signals && typeof snapshot.signals === 'object' ? Object.values(snapshot.signals) : [];
- for (const signal of signals) controls.consume(signal as ChatEvent);
- }
- if (event.type === 'run_started') { runId = String(event.run_id); cancel.disabled = false; }
- if (typeof event.event_offset === 'number') {
- if (event.event_offset <= offset) return;
- offset = event.event_offset;
- }
- if (event.type === 'content' || event.type === 'ai_message') {
- assistant ??= row('assistant', '');
- assistant.textContent += String(event.delta || event.content || '');
- }
- if (event.type === 'content_replace') {
- assistant ??= row('assistant', ''); assistant.textContent = String(event.content || event.text || '');
- }
- if (event.type === 'error') throw new Error(String(event.message || event.error || 'Run failed'));
-}
-async function follow(events: AsyncIterable) {
- busy(true); status.textContent = 'Working';
- try {
- for await (const event of events) consume(event);
- status.textContent = 'Complete'; runId = ''; await historyMessages();
- } catch (error) {
- if (error instanceof DOMException && error.name === 'AbortError') { status.textContent = 'Cancelled'; runId = ''; }
- else status.textContent = String(error);
- }
- finally { busy(false); }
-}
-element('chat-form').addEventListener('submit', event => {
- event.preventDefault(); if (running) return;
- const text = input.value.trim(); if (!text) return;
- void (async () => {
- busy(true);
- try {
- if (!chatId) rememberChat((await client.createChat()).chat_id);
- input.value = ''; row('user', text); assistant = null; offset = 0; controller = new AbortController();
- const files = attachment ? [attachment] : [];
- attachment = null; element('attachment').textContent = '';
- await follow(client.send({ chat_id: chatId, message: text, ...(files.length ? { attachments: files } : {}) }, controller.signal));
- } catch (error) { status.textContent = String(error); busy(false); }
- })();
-});
-cancel.addEventListener('click', () => {
- void (async () => {
- try { await client.cancel(runId); controller?.abort(); status.textContent = 'Cancelled'; }
- catch (error) { status.textContent = String(error); }
- })();
-});
-resume.addEventListener('click', () => { controller = new AbortController(); void follow(client.resume(runId, offset, controller.signal)); });
-element('new').addEventListener('click', () => {
- if (running) return; rememberChat(''); runId = ''; offset = 0; attachment = null;
- element('attachment').textContent = ''; input.value = '';
- (element('file')).value = '';
- messages.replaceChildren(); controls.clear(); assistant = null; status.textContent = 'Ready'; busy(false);
-});
-element('file').addEventListener('change', event => {
- const file = (event.target as HTMLInputElement).files?.[0]; if (!file || running) return;
- busy(true);
- void (async () => {
- try {
- if (!chatId) rememberChat((await client.createChat()).chat_id);
- attachment = await client.upload(file, chatId); element('attachment').textContent = attachment.name;
- } catch (error) { status.textContent = String(error); }
- finally { busy(false); }
- })();
-});
-void (async () => {
- busy(true);
- try {
- await historyMessages();
- if (!chatId) return;
- await controls.recover();
- const active = await client.activeRun(chatId);
- if (active?.run_id && !['completed', 'failed', 'cancelled'].includes(String(active.status))) {
- runId = String(active.run_id); controller = new AbortController();
- await follow(client.subscribe(runId, controller.signal));
- }
- } catch (error) { status.textContent = String(error); }
- finally { busy(false); }
-})();
diff --git a/examples/customer-chat/src/theme.css b/examples/customer-chat/src/theme.css
deleted file mode 100644
index f708d6b3..00000000
--- a/examples/customer-chat/src/theme.css
+++ /dev/null
@@ -1,19 +0,0 @@
-[data-chat-customization="customer-portal"] .customer-welcome {
- display: flex; flex-direction: column; height: 100%; min-height: 0;
-}
-[data-chat-customization="customer-portal"] .customer-welcome > header {
- display: flex; flex-wrap: wrap; align-items: center; gap: 16px; padding: 16px;
- color: var(--color-text); background: var(--color-bg-container); border-bottom: 1px solid var(--color-border);
-}
-[data-chat-customization="customer-portal"] button {
- color: var(--color-primary); background: var(--color-bg-container); border: 1px solid var(--color-border);
- border-radius: var(--radius-sm); padding: 8px 16px; cursor: pointer;
-}
-[data-chat-customization="customer-portal"] .customer-message { min-width: 0; }
-[data-chat-customization="customer-portal"] .customer-message-label {
- display: block; margin: 8px 20px 0; color: var(--color-text-secondary); font-size: 12px;
-}
-[data-chat-customization="customer-portal"] .customer-composer { width: 100%; min-width: 0; }
-[data-chat-customization="customer-portal"] .customer-composer-status {
- display: block; text-align: right; color: var(--color-text-secondary); font-size: 12px; padding: 0 16px;
-}
diff --git a/examples/customer-chat/vite.config.mjs b/examples/customer-chat/vite.config.mjs
deleted file mode 100644
index 9fbfbe95..00000000
--- a/examples/customer-chat/vite.config.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import { defineConfig, loadEnv } from 'vite';
-import { resolve } from 'node:path';
-import { createDeveloperProxy } from './scripts/developer-proxy.mjs';
-
-export default defineConfig(({ mode }) => {
- const env = { ...loadEnv(mode, process.cwd(), 'HUGAGENT_'), ...process.env };
- const host = env.HUGAGENT_DEV_HOST || '127.0.0.1';
- const settings = { host, port: Number(env.HUGAGENT_DEV_PORT || 4310), strictPort: true,
- allowedHosts: ['localhost'], cors: false, fs: { strict: true, allow: [process.cwd()] } };
- const originGuard = { name: 'developer-origin-guard', configureServer: guard, configurePreviewServer: guard };
- function guard(server) {
- server.middlewares.use((req, res, next) => {
- if (!/^\/(api(?:\/|$)|login(?:[?]|$))/.test(req.url || '')) return next();
- const origin = req.headers.origin;
- if (req.headers['sec-fetch-site'] === 'cross-site' || (origin && origin !== 'http://' + req.headers.host)) {
- res.statusCode = 403; res.end('Cross-origin developer API request refused'); return;
- }
- next();
- });
- }
- return {
- plugins: [originGuard],
- server: { ...settings, proxy: createDeveloperProxy(env) },
- preview: { host, port: Number(env.HUGAGENT_DEV_PORT || 4310), strictPort: true, proxy: createDeveloperProxy(env) },
- resolve: { dedupe: ['react', 'react-dom', '@hugagent/chat-sdk'] },
- build: { outDir: 'web-dist', sourcemap: false, rollupOptions: { input: {
- chat: resolve('index.html'), preview: resolve('preview.html'), debug: resolve('debug.html'), session: resolve('session.html'),
- } } },
- };
-});
diff --git a/install.sh b/install.sh
index b1333327..494ab979 100755
--- a/install.sh
+++ b/install.sh
@@ -339,7 +339,7 @@ pip_install --upgrade \
"pymilvus==2.5.18" \
"milvus-lite==3.1.0"
info "Installing Agent Skills Python dependencies"
-pip_install -r docker/requirements-script-runner.txt
+pip_install -r docker/sandbox/requirements-script-runner.txt
pip_install --no-deps -e .
SKILL_NODE_DIR="${HUGAGENT_DATA_DIR}/node"
@@ -365,7 +365,7 @@ info "Building the web application"
VITE_EDITION=ce VITE_DEFAULT_LANGUAGE=en NODE_OPTIONS="${BUILD_NODE_OPTIONS# }" npm run build
)
-HUGAGENT_BIN="${VENV_DIR}/bin/hugagent"
+HUGAGENT_BIN="${VENV_DIR}/bin/hugagent-server"
[[ -x "${HUGAGENT_BIN}" ]] || die "The HugAgentOS command was not installed correctly."
printf '\n'
diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore
new file mode 100644
index 00000000..b9470778
--- /dev/null
+++ b/packages/cli/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
diff --git a/packages/cli/DESIGN.md b/packages/cli/DESIGN.md
new file mode 100644
index 00000000..ad264c24
--- /dev/null
+++ b/packages/cli/DESIGN.md
@@ -0,0 +1,218 @@
+# hugagent 终端客户端设计(对标 Codex CLI / pi)
+
+状态:草案,未实现。已定取舍:TUI 用 TypeScript + `@mariozechner/pi-tui`;local 模式复用现有后端运行时。
+
+## 1. 目标
+
+- 交互体验对标 pi:内联差分渲染(保留终端原生回滚,不占全屏),多行编辑器,流式 Markdown,工具调用一行一条,运行中可插话。
+- 能力对标 Codex CLI:审批与沙箱档位、会话恢复、非交互 `exec`、JSONL 事件输出、MCP、`/review` `/diff`。
+- `/mode` 切换执行位置:
+ - `local`:全部是本机进程,agent 直接操作当前目录。
+ - `cloud`:连接远端 HugAgentOS 服务。
+ - `device:`:远程运维另一台已配对的电脑。
+- 两种模式用同一套界面、同一套事件协议,切换后体验一致。
+
+不做:在 TypeScript 里重写 agent 循环。agent、工具、沙箱、压缩、技能、记忆全部由后端负责。
+
+## 2. 架构
+
+```
+┌──────────────── hugagent(Node,packages/cli)───────────────┐
+│ 入口:interactive │ -p/exec │ --output json │ --output rpc │
+│ TUI:pi-tui(Editor / Markdown / SelectList / Loader) │
+│ Engine 接口 ── send / subscribe / steer / cancel / 审批 / 会话 │
+│ ├─ LocalEngine → http://127.0.0.1:<随机端口>(本机子进程) │
+│ └─ RemoteEngine → https://(cloud / device:) │
+└──────────────────────────────┬───────────────────────────────┘
+ │ 同一套 HTTP + SSE(@hugagent/chat-sdk)
+┌──────────────────────────────┴───────────────────────────────┐
+│ Python 后端(现有):agent 运行时 · 工具 · OS 沙箱 · 压缩 · 技能 · MCP │
+│ local:hugagent-server engine(SQLite,~/.hugagent,绑定 loopback)│
+└──────────────────────────────────────────────────────────────┘
+```
+
+### 为什么本机引擎走 loopback HTTP,而不是 stdio RPC
+
+调整说明:讨论取舍时写的是 stdio JSON-RPC,这里改成 loopback HTTP。理由如下:
+
+- 后端已有完整的 SSE 事件协议,包括 `run_snapshot` 断线续播、`run.steer` 插话,以及 `tool-approval`、`file-confirm`、`user-questions` 带外审批。`@hugagent/chat-sdk` 已经封装了 `send / resume / subscribe`。走 HTTP,local 和 cloud 之间只差 base URL 和凭据。
+- 如果改成 stdio RPC,就要为同一套能力再定义第二种协议,两边会慢慢分叉。
+- 桌面端已经在 32101 端口跑着本机后端。CLI 检测到它(并且数据目录相同)时可以直接接入,和桌面端共享会话、不再重复启动。
+- pi 风格的 `--output rpc` 仍然保留,但它是 CLI **对外**提供的嵌入协议,不是 CLI 和后端之间的协议。
+
+### LocalEngine 生命周期
+
+1. 先找可复用的后端:读 `~/.hugagent/engine.json`(端口、令牌、PID),健康检查通过就直接接入。
+2. 找不到就启动 `hugagent-server engine --port 0 --token-file …`:
+ - 无头运行,不开浏览器、不托管前端静态文件;
+ - 只绑定 127.0.0.1;
+ - 就绪后把端口写进就绪文件。
+3. 鉴权:一次性随机令牌,复用桌面桥的机制(`HUGAGENT_DESKTOP_BRIDGE_SECRET` 这一类);不需要本机账号密码。
+4. 引擎空闲 N 分钟后自动退出;CLI 退出不强制关闭它,下次启动就是热启动。
+5. 当前目录映射:按 realpath 查找或创建 `kind=local` 的项目,挂载到 `/workspace/local/`(`core/sandbox/workspace/mounts.py`)。
+ - 新会话都建在这个项目里,根目录的 `AGENTS.md` 由现有逻辑读取(`core/projects/instructions.py`)。
+ - 命令执行用现有 OS 沙箱:Seatbelt / bwrap / Windows 低权限令牌。
+6. 首次使用时没有模型配置:TUI 内引导完成(复用 `cli.py` 的 `configure_model` 逻辑),不跳到网页。
+
+### RemoteEngine
+
+- `cloud`:用 `/login` 获取个人 API Key 并存进系统钥匙串,回退到 `~/.hugagent/cli.json`(权限 0600)。默认要求 HTTPS,只有 loopback 和显式允许的内网地址例外。
+- 云端 agent **碰不到本机当前目录**。状态栏必须常驻显示 `cloud · 无本地文件访问`,`@文件` 只能以附件上传的方式提供。
+- `device:`:沿用现有 session-manager 操作队列(`hugagent_cli/contract.py`)。这台设备上的对话由那台机器执行,TUI 只能轮询结果,不能实时流式显示(设备链路目前没有 SSE)。
+
+## 3. `/mode` 语义
+
+| 操作 | 行为 |
+|---|---|
+| `/mode` | 弹出选择列表:local / cloud / 在线设备,并标出当前项 |
+| `/mode local\|cloud\|device:` | 直接切换。当前会话留在原来那一侧,目标侧开新会话;`/resume` 只列目标侧、当前项目的会话 |
+| `--mode` 启动参数 / 配置文件 `defaultMode` | 启动时的默认值;不指定时,如果当前目录是 git 仓库就用 local,否则用上次的选择 |
+| 状态栏 | `local · ~/repo · deepseek-chat · 审批:按需 · ctx 42%` |
+
+- 第一版不支持把会话从一侧搬到另一侧。以后可以加 `/handoff`:把摘要和关键文件带到另一侧,开一个新会话。
+- 命名提醒:pi 的 `--mode` 表示输出格式。我们的 `--mode` 表示执行位置,输出格式统一改用 `--output text|json|rpc`。
+
+## 4. 功能对标矩阵
+
+Codex / pi 两列按公开版本归纳,细节以官方文档为准。"后端"列表示后端现在是否已经具备这项能力。
+
+| 能力 | Codex | pi | 我们的做法 | 后端 | 阶段 |
+|---|---|---|---|---|---|
+| 内联差分渲染、原生回滚 | 全屏 TUI | ✓ | pi-tui | — | P1 |
+| 多行编辑器、历史、快捷键 | ✓ | ✓ | pi-tui Editor | — | P1 |
+| 流式 Markdown、思考折叠 | ✓ | ✓ | 渲染 `content` / `thinking` 事件 | ✓ | P1 |
+| 工具调用一行显示、可展开 | ✓ | ✓ | `tool_call_*` / `tool_result` / `subagent_event` | ✓ | P1 |
+| Esc 中断 | ✓ | ✓ | `run.cancel` | ✓ | P1 |
+| 运行中插话 / 排到下一条 | 排队 | Enter 插话 / Alt+Enter 排队 | `run.steer`,`delivery_mode=steer\|followUp\|nextRun` | ✓ | P1 |
+| 工具与命令审批 | approval policy | 默认不拦 | 终端内回应 `tool-approval` / `file-confirm` | ✓ | P1 |
+| 向用户提问 | — | — | `user_question` 内联选择 | ✓ | P1 |
+| 沙箱档位 `/permissions` | read-only / workspace-write / full | — | 映射现有权限预设 + OS 沙箱 | ✓ | P2 |
+| `/model`、思考档位 | ✓ | ✓(Ctrl+P / Shift+Tab) | `ChatRequest.model_name / model_provider_id`,思考档位走偏好设置 | ✓ | P1 |
+| 会话恢复 `-c` / `-r` | `resume` | ✓ | 服务端对话按项目(当前目录)过滤 | ✓ | P1 |
+| 分支 `/fork`、`/tree`、回到旧消息重写 | Esc Esc 回退 | ✓ | `POST /chats/{id}/fork` | ✓ | P2 |
+| 手动 `/compact` | ✓ | ✓ | 后端只有自动压缩,**需新增接口** | 部分 | P2 |
+| 上下文用量显示 | ✓ | ✓ | `context_usage` 事件 | ✓ | P1 |
+| AGENTS.md | 逐级向上 + 全局 | 逐级向上 + 全局 | 只读项目根目录,**需补逐级合并和全局文件** | 部分 | P2 |
+| `@文件` 模糊引用 | ✓ | ✓ | local:路径交给 agent;cloud:作为附件上传 | 部分 | P2 |
+| `!命令` 直接执行 | ✓ | ✓ | local 在本机沙箱执行,把输出注入上下文 | — | P2 |
+| 粘贴图片 | ✓ | ✓ | 附件 + 视觉桥 | ✓ | P2 |
+| `/diff`、`/review` | ✓ | 扩展实现 | local 由客户端读 git;review 作为一种对话模式 | — | P2 |
+| 非交互 `exec` / `-p` | ✓ | ✓ | `hugagent -p "…"`,退出码反映运行结果 | ✓ | P0 |
+| JSONL 事件流 | `--json` | `--mode json` | `--output json`,原样输出 SSE 事件 | ✓ | P0 |
+| 嵌入协议 | app-server | `--mode rpc` | `--output rpc`(stdin/stdout JSON) | — | P2 |
+| MCP | ✓ | 不内置 | 后端插件市场;提供 `hugagent mcp list/add` | ✓ | P3 |
+| 技能 / 提示模板 / 扩展 | ✓ | ✓ | 后端技能 + 插件;TUI 侧扩展暂不做 | ✓ | P3 |
+| 登录 | ChatGPT / API Key | 订阅 / API Key | `/login`(cloud);local 的模型凭据存在本机库 | 部分 | P1 |
+| 主题、按键绑定 | 部分 | ✓ | `~/.hugagent/cli.json` | — | P3 |
+| 导出 / 分享 | — | HTML / gist | `/export` Markdown 或 HTML | — | P3 |
+| 远程运维 | — | — | `/mode device:`、`/sessions`、`/runs`、`/devices` | ✓ | P3 |
+| 子 agent、计划、自主循环 | 部分 | 扩展 | 渲染 `subagent_event` / `plan_update`(我们的优势) | ✓ | P2 |
+| 分发、升级 | npm / brew | npm | npm 全局安装;local 引擎复用桌面端自带的 Python 运行时,或 `pip install hugagent` | — | P3 |
+
+## 5. 后端需要补的东西
+
+1. `hugagent-server engine` 子命令:无头运行、`--port 0`、就绪文件、loopback 令牌鉴权、空闲退出、父进程监护。
+2. 按路径查找或创建本地项目的接口(目前只有桌面端授权文件夹的流程)。
+3. 手动压缩接口,对应 `/compact`。
+4. `ChatRequest.message` 的长度上限 10000 字符,对粘贴代码太小。附件可以绕开,但 `-p` 和管道输入需要更大的上限,或者自动转成附件。
+5. AGENTS.md 逐级向上合并 + 全局文件 `~/.hugagent/AGENTS.md`。
+6. 统一命名:
+ - Python 的 console script 改名 `hugagent-server`(负责 onboard / serve / doctor / engine);
+ - npm 包占用 `hugagent`;`hugagent serve` 等命令转发给 `hugagent-server`;
+ - 云电脑镜像里的零依赖会话客户端 `hugagent_cli` 保持不变,因为 Personal Agent 依赖它。
+ - 上次审查发现的入口路由、错误详情丢失、空响应、操作表清理等问题,在这一步一起修。
+
+## 6. 包结构
+
+```
+packages/cli/
+ src/
+ main.ts 参数解析,分发到各模式
+ config.ts ~/.hugagent/cli.json、钥匙串
+ engine/
+ engine.ts Engine 接口 + 统一事件类型(复用 chat-sdk 类型)
+ local.ts 发现 / 启动 / 接入本机引擎,当前目录 → 本地项目
+ remote.ts cloud 与 device:
+ modes/
+ interactive.ts TUI 主循环
+ print.ts -p / exec
+ json.ts --output json
+ rpc.ts --output rpc
+ tui/
+ transcript.ts 事件 → 组件(正文 / 思考 / 工具行 / 计划 / 子 agent)
+ footer.ts 状态栏
+ prompts/ 审批、提问、选择列表
+ commands/ /mode /model /resume /new /compact /permissions /fork /diff …
+ test/
+```
+
+## 7. 分期与验收
+
+- **P0 骨架**:包结构、两种 Engine、`-p` 和 `--output json`。
+ - 验收:在任意仓库目录执行 `hugagent -p "列出这个目录的文件"`,local 模式由本机引擎在当前目录完成;`--mode cloud` 走远端服务;两边的 JSONL 事件一致。
+- **P1 交互 TUI**:编辑器、流式渲染、工具行、状态栏、Esc 中断、插话 / 排队、审批与提问内联处理、`/mode` `/model` `/new` `/resume` `/login` `/quit`。
+ - 验收:一次完整的"改代码 → 审批 → 跑测试"闭环不离开终端。
+- **P2 对标补齐**:`@文件`、`!命令`、图片、`/compact`、`/permissions`、`/fork` `/tree`、`/diff` `/review`、AGENTS.md 层级、`--output rpc`。
+- **P3 生态与运维**:`device:`、`/sessions` `/runs` `/devices`、MCP / 技能命令、主题按键、导出、npm 发布与升级。
+
+## 8. 实现契约(并行开发时各方只认这一节)
+
+### 8.1 本机引擎(Python 侧提供,`LocalEngine` 消费)
+
+- 启动:`hugagent-server engine [--port 0] [--idle-timeout 900] [--ready-file PATH]`;开发态等价于 `python src/backend/cli.py engine …`。数据目录沿用 `HUGAGENT_HOME`(默认 `~/.hugagent`)。
+- 只绑定 127.0.0.1;不开浏览器,不要求前端 dist。
+- 就绪文件:默认 `$HUGAGENT_HOME/engine.json`,权限 0600,原子写入,在服务能处理请求之后才写。内容:
+ ```json
+ {"version":1,"url":"http://127.0.0.1:","token":"sk-jx-…","pid":123,"started_at":"","home":"<绝对路径>"}
+ ```
+- `token` 是本机管理员的个人 API Key(名称 `hugagent-cli`),可作为 `Authorization: Bearer` 访问 `{url}/v1/*` 下的全部接口。
+- 未初始化(还没有管理员)时:退出码 3,stderr 输出 `{"error":"not_initialized"}`。
+- 空闲退出:`--idle-timeout` 秒内既没有 HTTP 请求、也没有活动 run,就优雅退出并删除就绪文件。
+- `LocalEngine` 的发现顺序:
+ 1. 读就绪文件,校验 pid 存活、`GET {url}/health` 返回 200;
+ 2. 否则按顺序找命令并以分离进程启动,日志写到 `$HUGAGENT_HOME/logs/engine.log`,最多等 120 秒出现就绪文件:
+ - 环境变量 `HUGAGENT_ENGINE_CMD`(JSON 数组);
+ - PATH 上的 `hugagent-server`;
+ - 设置了 `HUGAGENT_REPO` 时,用 `[${HUGAGENT_PYTHON:-python3}, "$HUGAGENT_REPO/src/backend/cli.py"]`。
+
+### 8.2 当前目录 → 本地项目(后端提供)
+
+- `POST /v1/projects/local/resolve`,请求体 `{"path":"<绝对路径>","create":true,"name":"<可选>"}`。
+- 返回 `success_response`,data 为 `{"project_id","name","local_path","created":bool}`。
+- 按 realpath 匹配已有的 `kind=local` 项目;不存在且 `create=true` 时按现有本地项目创建流程新建。非本机模式返回 404。
+- AGENTS.md 层级(只影响本地项目的提示词组装),由外到内合并:
+ 1. 全局 `$HUGAGENT_HOME/AGENTS.md`;
+ 2. 从最近的 git 根目录(找不到时以 `$HOME` 为界)一路往下,直到项目目录,每一级目录里的 `AGENTS.md`。
+
+ 项目根目录那份仍然是唯一可通过 API 编辑的文件。
+
+### 8.3 手动压缩与消息长度(后端提供)
+
+- `POST /v1/chats/{chat_id}/compact`:同步压缩该会话上下文。返回 data `{"compacted":bool,"tokens_before":int|null,"tokens_after":int|null}`;会话有活动 run 时返回 409。
+- `ChatRequest.message` 的上限从 10000 提高到 100000 字符。
+- 插话(`SteerChatRunRequest.message`)保持 10000,不改数据库:它落在 `chat_steer_queue` 表的 CHECK 约束里,要改得同时动 EE 迁移、CE 迁移和本地 SQLite 重建表。插话本来就是简短的中途指令,长内容应该作为普通消息发送。TUI 在插话超长时提示用户改为普通消息。
+- `hugagent_cli/contract.py` 的会话代理上限不变。
+
+### 8.4 CLI 命令行(`packages/cli`)
+
+- `hugagent [prompt]`:交互 TUI。
+- `hugagent -p/--print `:非交互;不给 prompt 时读 stdin。
+- 输出格式 `--output text|json|rpc`:
+ - `json`:每个 SSE 事件原样输出为一行 JSON,最后追加一行 `{"type":"cli_result","status":"completed|failed|cancelled","chat_id","run_id"}`;
+ - `rpc`:从 stdin 读 JSON 行命令 `{"id","method","params"}`,回应 `{"id","result"|"error"}`,事件以 `{"event":{…}}` 推送;方法与 Engine 接口一一对应。
+- `--mode local|cloud|device:`;`-c/--continue`;`-r/--resume [chat_id]`;`--model :`;`--server `(cloud)。
+- 非交互模式遇到 `file_confirm` 默认拒绝,加 `--auto-approve` 时允许;遇到 `user_question` 一律 skip。
+- 子命令:
+ - `hugagent login [--server URL]` / `logout`:凭据存 `~/.hugagent/cli.json`(0600);
+ - `hugagent serve|onboard|doctor`:转发给 `hugagent-server`。
+- `--version`、`--help`。
+- 退出码:0 完成,1 失败,2 用法错误,3 需要初始化,130 中断。
+- 交互模式入口固定为 `src/modes/interactive.ts` 的 `runInteractive(options: RunOptions)`。UI 只依赖 `src/engine/engine.ts`。
+
+## 9. 风险
+
+- **Python 引擎冷启动慢**:完整 lifespan 要数秒。先实测;用"接入已运行的后端 + 空闲保活"缓解;engine 子命令只启动 agent 运行需要的步骤。
+- **两套运行时**:只用 cloud 的用户只需要 Node;local 需要 Python 运行时,优先复用桌面端已安装的那份。
+- **pi-tui 还是 0.x**:锁定精确版本,用自己的组件层隔离它的 API。
+- **终端兼容**:中文输入法和宽字符(pi-tui 依赖 get-east-asian-width),Windows Terminal,SSH 下的剪贴板。
+- **cloud 不能访问本机目录**:必须在界面上一直说清楚,否则用户会以为云端改了本地代码。
diff --git a/packages/cli/README.md b/packages/cli/README.md
new file mode 100644
index 00000000..34966311
--- /dev/null
+++ b/packages/cli/README.md
@@ -0,0 +1,126 @@
+# hugagent
+
+HugAgentOS in your terminal. The agent, tools, sandbox, compaction, skills and memory all run in the
+HugAgentOS backend; this package is the client: a pi-style TUI, a one-shot `-p` mode, a JSONL event
+stream and a JSON-lines embedding protocol. Design: [DESIGN.md](DESIGN.md) (§8 is the contract).
+
+## Install
+
+Local mode needs the Python backend's `hugagent-server` command. From the repository root:
+
+```bash
+python3 -m venv .venv && source .venv/bin/activate
+pip install -r requirements.txt && pip install --no-deps -e .
+hugagent-server onboard --no-serve # once: admin account and main model
+
+(cd packages/chat-sdk && npm install && npm run build)
+(cd packages/cli && npm install && npm run build && npm install -g .)
+
+cd ~/code/my-project && hugagent
+```
+
+`npm install -g .` links the checkout, so `npm run build` is enough after a change. To run from the checkout
+without a global install, use `node packages/cli/dist/main.js`. If `hugagent-server` is not on `PATH`, point the
+client at the repo with `HUGAGENT_REPO=` and `HUGAGENT_PYTHON=/.venv/bin/python`.
+
+## Where the agent runs: `--mode`
+
+| Mode | What it talks to | Local files |
+|---|---|---|
+| `local` (default) | the shared headless engine on this machine (`hugagent-server engine`) | the current directory, bound as a local project |
+| `cloud` | a remote HugAgentOS (`hugagent login`) | none: attach files instead |
+| `device:` | another paired computer, through the cloud's session-manager queue | that computer's |
+
+The default is `--mode`, then `defaultMode` in `$HUGAGENT_HOME/cli.json`, then `local`.
+
+### local
+
+1. Reads `$HUGAGENT_HOME/engine.json` (default `~/.hugagent`); if its pid is alive and `GET /health` answers 200, attaches.
+2. Otherwise starts the engine detached, logging to `$HUGAGENT_HOME/logs/engine.log`, and waits up to 120 s for the ready file.
+ The command is the first of: `HUGAGENT_ENGINE_CMD` (JSON array, e.g. `["python3","/repo/src/backend/cli.py"]`),
+ `hugagent-server` on `PATH`, `[${HUGAGENT_PYTHON:-python3}, "$HUGAGENT_REPO/src/backend/cli.py"]`.
+3. Binds the current directory with `POST /v1/projects/local/resolve`; new sessions go into that project and
+ `-c` / `-r` only see its sessions.
+
+The engine is shared and idles out by itself; `hugagent` never stops it. A first start takes about 10 s,
+later ones about 6 s, an attach about 0.1 s. A machine that was never set up exits 3: run `hugagent onboard --no-serve`.
+
+### cloud and device
+
+```bash
+hugagent login --server https://agent.example.com/api # prompts for a personal API key, verifies it
+hugagent --mode cloud
+hugagent --mode device: -p "what is running?"
+hugagent logout
+```
+
+Credentials live in `$HUGAGENT_HOME/cli.json` (mode 0600); `HUGAGENT_SERVER` / `HUGAGENT_API_KEY` override it,
+and the saved key is only sent to the server it was saved for. Plain `http://` is refused except for loopback
+or with `HUGAGENT_ALLOW_HTTP=1`. The server URL is the API root: include `/api` behind the default nginx gateway.
+
+Device mode has no live stream: a turn shows `run_started`, then the final answer once the run settles. It cannot
+fork, compact, approve, answer questions, pick models or change permission modes.
+
+## Non-interactive
+
+```bash
+hugagent -p "list the files here" # answer on stdout, tool lines and notes on stderr
+git diff | hugagent -p "review this" # no prompt: read stdin (or pass -p -)
+hugagent -p "fix the test" --output json # every SSE event as a line, then a cli_result line
+hugagent -c -p "and now the docs" # continue the latest session of this directory
+hugagent -r chat_59b5… -p "go on" # a given session
+```
+
+The last JSON line is `{"type":"cli_result","status":"completed|failed|cancelled","chat_id","run_id"}`
+(plus `error` when it did not complete). Events are passed through unchanged; see
+`.claude/skills/hugagent-frontend-dev/references/sse-events.md`.
+
+Approvals: a `file_confirm` is denied unless `--auto-approve`; every `user_question` is skipped. Ctrl+C cancels
+the run on the server and exits 130 (a second Ctrl+C exits at once).
+
+Exit codes: 0 completed, 1 failed, 2 usage error, 3 setup required (`onboard` or `login`), 130 interrupted.
+
+## `--output rpc`
+
+One JSON command per stdin line, replies and events on stdout. Commands run concurrently, so `cancel`, `steer`
+and `confirm` work while a `send` streams.
+
+```jsonc
+→ {"id":1,"method":"newSession","params":{"title":"from my editor"}}
+← {"id":1,"result":{"chatId":"chat_…","title":"from my editor","projectId":"prj_…"}}
+→ {"id":2,"method":"send","params":{"chatId":"chat_…","message":"hi"}}
+← {"event":{"type":"run_started","run_id":"…"},"request":2}
+← {"event":{"type":"content","delta":"Hello"},"request":2}
+← {"id":2,"result":{"status":"completed","chatId":"chat_…","runId":"…"}}
+```
+
+Methods mirror `src/engine/engine.ts`: `info`, `connect`, `close`, `newSession {title?}`, `listSessions {limit?}`,
+`history {chatId, limit?}`, `fork {chatId, messageId?}`, `send {chatId, message, model?, attachments?}`,
+`resume {runId, fromOffset?}`, `activeRun {chatId}`, `cancel {runId}`, `steer {runId, message, mode?}`,
+`confirm {chatId, confirmId, decision, optionId?}`, `answer {chatId, requestId, answers}`, `compact {chatId}`,
+`listModels`, `getPermissionMode`, `setPermissionMode {mode}`. Errors are
+`{"id","error":{"message","status"?,"code"?,"exitCode"?}}`. No approval policy applies: the client answers
+`file_confirm` and `user_question` itself. stdin EOF waits for running commands, then exits 0.
+
+## Server commands
+
+`hugagent serve|onboard|doctor …` runs `hugagent-server` (found the same way as the engine) with the terminal and
+passes its exit code on.
+
+## Layout
+
+```
+src/main.ts arguments, login/logout, server forwarding, mode dispatch
+src/config.ts $HUGAGENT_HOME/cli.json, server URL policy, credentials
+src/engine/engine.ts the Engine interface the UI is built against
+src/engine/http.ts authenticated HTTP over @hugagent/chat-sdk, envelope + error handling
+src/engine/base.ts shared HTTP engine (reconnects a dropped stream from its last offset)
+src/engine/local.ts engine discovery / spawn, cwd → local project
+src/engine/remote.ts cloud and device:
+src/engine/headless.ts one non-interactive turn: session choice, approval policy, Ctrl+C
+src/engine/index.ts EngineFactory
+src/modes/ interactive (TUI), print, json, rpc
+test/ node --test against a fake HugAgentOS (node:http + SSE)
+```
+
+`npm test` builds and runs the tests.
diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json
new file mode 100644
index 00000000..63db7171
--- /dev/null
+++ b/packages/cli/package-lock.json
@@ -0,0 +1,179 @@
+{
+ "name": "@hugagent/cli",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@hugagent/cli",
+ "version": "0.1.0",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@hugagent/chat-sdk": "file:../chat-sdk",
+ "@mariozechner/pi-tui": "0.73.1",
+ "chalk": "^5.5.0"
+ },
+ "bin": {
+ "hugagent": "dist/main.js"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.0",
+ "typescript": "~5.9.3"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "../chat-sdk": {
+ "name": "@hugagent/chat-sdk",
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "@types/react": "^19.2.7",
+ "react": "^19.2.0",
+ "typescript": "~5.9.3"
+ },
+ "peerDependencies": {
+ "react": ">=19 <20"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@hugagent/chat-sdk": {
+ "resolved": "../chat-sdk",
+ "link": true
+ },
+ "node_modules/@mariozechner/pi-tui": {
+ "version": "0.73.1",
+ "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.73.1.tgz",
+ "integrity": "sha512-ybVsRnUbzQRtbocltJ2OXb2QogrO67N2BlUyKjZz9BHcZYiDJtNkcKQockxDjsVvDc0uBCLDX6iZJoBElBd8fw==",
+ "deprecated": "please use @earendil-works/pi-tui instead going forward",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime-types": "^2.1.4",
+ "chalk": "^5.5.0",
+ "get-east-asian-width": "^1.3.0",
+ "marked": "^15.0.12",
+ "mime-types": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "optionalDependencies": {
+ "koffi": "^2.9.0"
+ }
+ },
+ "node_modules/@types/mime-types": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
+ "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
+ "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz",
+ "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/koffi": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.3.tgz",
+ "integrity": "sha512-E9y1AsgYGlaxMhcZzHr8y96QF2U5XzA12GGVAfbWqIubTwPNMXQarfBzePNXHe0xtIEtNd6ifAv3GAKYGUeBAQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "url": "https://liberapay.com/Koromix"
+ }
+ },
+ "node_modules/marked": {
+ "version": "15.0.12",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
+ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ }
+ }
+}
diff --git a/packages/cli/package.json b/packages/cli/package.json
new file mode 100644
index 00000000..47993e03
--- /dev/null
+++ b/packages/cli/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@hugagent/cli",
+ "version": "0.1.0",
+ "description": "HugAgentOS terminal client: local and cloud agent sessions in a pi-style TUI",
+ "type": "module",
+ "license": "UNLICENSED",
+ "bin": {
+ "hugagent": "dist/main.js"
+ },
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "engines": {
+ "node": ">=20"
+ },
+ "scripts": {
+ "build": "tsc -p tsconfig.json",
+ "test": "npm run build && node --test test/*.test.mjs"
+ },
+ "dependencies": {
+ "@hugagent/chat-sdk": "file:../chat-sdk",
+ "@mariozechner/pi-tui": "0.73.1",
+ "chalk": "^5.5.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.0",
+ "typescript": "~5.9.3"
+ }
+}
diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts
new file mode 100644
index 00000000..924680b3
--- /dev/null
+++ b/packages/cli/src/config.ts
@@ -0,0 +1,112 @@
+/**
+ * CLI settings and cloud credentials in $HUGAGENT_HOME/cli.json (default ~/.hugagent/cli.json, mode 0600).
+ * `hugagent login` writes `{server, apiKey}`; HUGAGENT_SERVER / HUGAGENT_API_KEY override them.
+ */
+import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
+import { homedir } from 'node:os';
+import { join, resolve } from 'node:path';
+import type { Mode } from './engine/engine.js';
+import { CliError, EXIT } from './engine/errors.js';
+
+export interface CliConfig {
+ server?: string;
+ apiKey?: string;
+ /** Mode used when --mode is not given. */
+ defaultMode?: string;
+ [key: string]: unknown;
+}
+
+export function hugagentHome(env: NodeJS.ProcessEnv = process.env): string {
+ const configured = env.HUGAGENT_HOME?.trim();
+ return configured ? resolve(configured) : join(homedir(), '.hugagent');
+}
+
+export function configPath(env: NodeJS.ProcessEnv = process.env): string {
+ return join(hugagentHome(env), 'cli.json');
+}
+
+export function loadConfig(env: NodeJS.ProcessEnv = process.env): CliConfig {
+ const path = configPath(env);
+ let text: string;
+ try {
+ text = readFileSync(path, 'utf8');
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
+ throw error;
+ }
+ try {
+ const parsed: unknown = JSON.parse(text);
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('not an object');
+ return parsed as CliConfig;
+ } catch {
+ throw new CliError(`${path} is not valid JSON; fix or delete it`, EXIT.usage);
+ }
+}
+
+/** Atomic write, owner-only: the file holds an API key. */
+export function saveConfig(config: CliConfig, env: NodeJS.ProcessEnv = process.env): void {
+ const home = hugagentHome(env);
+ mkdirSync(home, { recursive: true, mode: 0o700 });
+ const path = configPath(env);
+ const temp = `${path}.${process.pid}.tmp`;
+ writeFileSync(temp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
+ chmodSync(temp, 0o600);
+ renameSync(temp, path);
+}
+
+const DEVICE_ID = /^[A-Za-z0-9_-]{1,100}$/;
+
+export function parseMode(value: string): Mode | null {
+ if (value === 'local' || value === 'cloud') return value;
+ if (value.startsWith('device:') && DEVICE_ID.test(value.slice(7))) return value as Mode;
+ return null;
+}
+
+const LOOPBACK = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
+
+export function isLoopbackHost(hostname: string): boolean {
+ return LOOPBACK.has(hostname) || hostname.endsWith('.localhost');
+}
+
+/** Normalized server URL; https is required except for loopback or HUGAGENT_ALLOW_HTTP=1. */
+export function validateServerUrl(value: string, env: NodeJS.ProcessEnv = process.env): string {
+ let url: URL;
+ try {
+ url = new URL(value);
+ } catch {
+ throw new CliError(`Invalid server URL: ${value}`, EXIT.usage);
+ }
+ if ((url.protocol !== 'http:' && url.protocol !== 'https:') || !url.hostname || url.username || url.password || url.search || url.hash) {
+ throw new CliError(`Invalid server URL: ${value} (expected https://host[/path])`, EXIT.usage);
+ }
+ if (url.protocol === 'http:' && !isLoopbackHost(url.hostname) && env.HUGAGENT_ALLOW_HTTP !== '1') {
+ throw new CliError(`Refusing plain HTTP to ${url.host}: use https, or set HUGAGENT_ALLOW_HTTP=1 for a trusted network`, EXIT.usage);
+ }
+ return url.toString().replace(/\/+$/, '');
+}
+
+export interface CloudCredentials { server: string; apiKey: string }
+
+/**
+ * Server: --server > HUGAGENT_SERVER > cli.json. API key: HUGAGENT_API_KEY > cli.json, and the saved
+ * key is only used for the server it was saved for, so --server never leaks it to another host.
+ */
+export function resolveCloudCredentials(serverFlag: string | undefined, env: NodeJS.ProcessEnv = process.env): CloudCredentials {
+ const config = loadConfig(env);
+ const raw = serverFlag || env.HUGAGENT_SERVER?.trim() || config.server;
+ if (!raw) throw new CliError('No cloud server configured: run `hugagent login --server https://…` or pass --server', EXIT.setup);
+ const server = validateServerUrl(raw, env);
+ const saved = config.server && config.apiKey && safeNormalize(config.server) === server ? config.apiKey : undefined;
+ const apiKey = env.HUGAGENT_API_KEY?.trim() || saved;
+ if (!apiKey) throw new CliError(`Not logged in to ${server}: run \`hugagent login --server ${server}\``, EXIT.setup);
+ if (/[\r\n]/.test(apiKey)) throw new CliError('API key must be a single line', EXIT.usage);
+ return { server, apiKey };
+}
+
+function safeNormalize(server: string): string | undefined {
+ try {
+ return validateServerUrl(server, { ...process.env, HUGAGENT_ALLOW_HTTP: '1' });
+ } catch {
+ return undefined;
+ }
+}
diff --git a/packages/cli/src/engine/base.ts b/packages/cli/src/engine/base.ts
new file mode 100644
index 00000000..c85b479f
--- /dev/null
+++ b/packages/cli/src/engine/base.ts
@@ -0,0 +1,201 @@
+/** Shared Engine implementation for every mode that talks the full HTTP + SSE API (local, cloud). */
+import type { ChatEvent, ChatHistoryMessage, ChatRequest, ChatSession, FileDecision, QuestionAnswer } from '@hugagent/chat-sdk';
+import type { ActiveRun, CompactSummary, DeliveryMode, Engine, EngineInfo, ModelOption, PermissionMode, SendOptions,
+ SessionSummary } from './engine.js';
+import { CliError, HugAgentOSHttpError, isAbortError } from './errors.js';
+import type { HugAgentOSHttp } from './http.js';
+
+const RECONNECT_ATTEMPTS = 5;
+export const STEER_LIMIT = 10_000;
+
+export function toSummary(session: ChatSession): SessionSummary {
+ return {
+ chatId: session.chat_id,
+ title: session.title || '',
+ updatedAt: typeof session.updated_at === 'string' ? session.updated_at : undefined,
+ projectId: typeof session.project_id === 'string' ? session.project_id : undefined,
+ };
+}
+
+/** Page size the list endpoints accept for a wanted count. */
+export function pageSizeFor(limit: number): number {
+ return Math.min(Math.max(limit, 1), 100);
+}
+
+/** Newest-first summaries of a page of chats, at most `limit`. */
+export function latestSummaries(sessions: ChatSession[], limit: number): SessionSummary[] {
+ return newestFirst(sessions.map(toSummary)).slice(0, limit);
+}
+
+/** `open` runs once and is shared by concurrent callers; a failed attempt is forgotten so the next call retries. */
+export function openOnce(open: () => Promise): () => Promise {
+ let pending: Promise | undefined;
+ return () => (pending ??= open().catch((error: unknown) => {
+ pending = undefined;
+ throw error;
+ }));
+}
+
+export function newestFirst(sessions: SessionSummary[]): SessionSummary[] {
+ return [...sessions].sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? ''));
+}
+
+export function delay(ms: number, signal?: AbortSignal): Promise {
+ return new Promise((resolve, reject) => {
+ if (signal?.aborted) return reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
+ const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
+ const onAbort = () => { clearTimeout(timer); reject(signal?.reason ?? new DOMException('Aborted', 'AbortError')); };
+ signal?.addEventListener('abort', onAbort, { once: true });
+ });
+}
+
+/** A dropped stream (not a refusal) that a resume from the last offset can repair. */
+function isDisconnect(error: unknown): boolean {
+ if (error instanceof HugAgentOSHttpError) return error.status === 0 || error.status === 502 || error.status === 503 || error.status === 504;
+ if (!(error instanceof Error)) return false;
+ return error.name === 'TypeError' || /SSE disconnected|terminated|ECONNRESET|socket/i.test(error.message);
+}
+
+export abstract class HttpEngine implements Engine {
+ abstract readonly info: EngineInfo;
+ protected client?: HugAgentOSHttp;
+ private readonly opening = openOnce(() => this.open());
+
+ /** Start or attach to the backend and set `this.client`. */
+ protected abstract open(): Promise;
+ abstract newSession(title?: string): Promise;
+ abstract listSessions(limit?: number): Promise;
+
+ connect(): Promise {
+ return this.opening();
+ }
+
+ async close(): Promise {
+ // Nothing to release: HTTP is stateless and the backend outlives this client.
+ }
+
+ protected get http(): HugAgentOSHttp {
+ if (!this.client) throw new CliError('Engine is not connected; call connect() first');
+ return this.client;
+ }
+
+ async history(chatId: string, limit = 50): Promise {
+ const pageSize = pageSizeFor(limit);
+ const collected: ChatHistoryMessage[] = [];
+ // order=desc picks the most recent page first, but each page is still in
+ // conversation order, so sort by sequence rather than reversing.
+ for (let page = 1; collected.length < limit; page++) {
+ const { items } = await this.http.messages(chatId, page, pageSize, 'desc');
+ collected.push(...items);
+ if (items.length < pageSize) break;
+ }
+ const seq = (message: ChatHistoryMessage) => (typeof message.chat_seq === 'number' ? message.chat_seq : 0);
+ return collected.sort((a, b) => seq(a) - seq(b)).slice(-limit);
+ }
+
+ async fork(chatId: string, messageId?: string): Promise {
+ return toSummary(await this.http.fork(chatId, messageId));
+ }
+
+ async *send(chatId: string, message: string, options: SendOptions = {}): AsyncIterable {
+ const request: ChatRequest = { chat_id: chatId, message };
+ if (options.attachments?.length) request.attachments = options.attachments;
+ if (options.model) {
+ request.model_provider_id = options.model.providerId;
+ request.model_name = options.model.modelName;
+ }
+ yield* this.follow(this.http.send(request, options.signal), options.signal);
+ }
+
+ resume(runId: string, fromOffset = 0, signal?: AbortSignal): AsyncIterable {
+ return this.follow(this.http.resume(runId, fromOffset, signal), signal, runId, fromOffset || undefined);
+ }
+
+ /** Relays a run's events; a dropped connection resumes from the last offset instead of failing. */
+ private async *follow(stream: AsyncIterable, signal?: AbortSignal, runId?: string,
+ offset?: number): AsyncGenerator {
+ let source = stream;
+ let attempts = 0;
+ for (;;) {
+ try {
+ for await (const event of source) {
+ if (event.type === 'run_started' && typeof event.run_id === 'string') runId = event.run_id;
+ if (typeof event.event_offset === 'number') {
+ if (offset !== undefined && event.event_offset <= offset) continue;
+ offset = event.event_offset;
+ }
+ attempts = 0;
+ yield event;
+ }
+ return;
+ } catch (error) {
+ if (signal?.aborted || isAbortError(error) || !runId || !isDisconnect(error) || attempts >= RECONNECT_ATTEMPTS) throw error;
+ attempts += 1;
+ await delay(400 * attempts, signal);
+ source = this.http.resume(runId, offset ?? 0, signal);
+ }
+ }
+ }
+
+ async activeRun(chatId: string): Promise {
+ const run = await this.http.activeRun(chatId);
+ if (!run || typeof run.run_id !== 'string') return null;
+ return { runId: run.run_id, offset: typeof run.last_event_offset === 'number' ? run.last_event_offset : undefined };
+ }
+
+ cancel(runId: string): Promise {
+ return this.http.cancel(runId);
+ }
+
+ async steer(runId: string, message: string, mode: DeliveryMode = 'steer'): Promise {
+ // chat_steer_queue still caps queued input at 10000 characters (ChatRequest allows 100000).
+ if (message.length > STEER_LIMIT) throw new CliError(`Messages sent while a run is working are limited to ${STEER_LIMIT} characters`);
+ await this.http.steer(runId, message, mode);
+ }
+
+ async confirm(chatId: string, confirmId: string, decision: FileDecision, optionId?: string): Promise {
+ await this.http.confirmFile(chatId, confirmId, decision, optionId);
+ }
+
+ async answer(chatId: string, requestId: string, answers: QuestionAnswer[]): Promise {
+ await this.http.answerQuestion(chatId, requestId, answers);
+ }
+
+ async compact(chatId: string): Promise {
+ try {
+ const result = await this.http.compact(chatId);
+ return {
+ compacted: result.compacted === true,
+ tokensBefore: result.tokens_before ?? null,
+ tokensAfter: result.tokens_after ?? null,
+ };
+ } catch (error) {
+ // FastAPI's own 404 for an unknown route; a missing chat carries its own message.
+ if (error instanceof HugAgentOSHttpError && error.status === 404 && error.message.startsWith('Not Found (')) {
+ throw new CliError('This server has no manual compaction endpoint (POST /v1/chats/{id}/compact); update it');
+ }
+ throw error;
+ }
+ }
+
+ async listModels(): Promise {
+ const caps = await this.http.modelCapabilities();
+ const models = caps.user_model_switch?.enabled ? caps.user_model_switch.models ?? [] : [];
+ return models
+ .filter((model) => model.provider_id && model.model_name)
+ .map((model) => ({
+ providerId: model.provider_id,
+ modelName: model.model_name,
+ label: model.display_name || model.model_name,
+ current: !!model.is_default,
+ }));
+ }
+
+ getPermissionMode(): Promise {
+ return this.http.getToolApproval();
+ }
+
+ async setPermissionMode(mode: PermissionMode): Promise {
+ await this.http.setToolApproval(mode);
+ }
+}
diff --git a/packages/cli/src/engine/engine.ts b/packages/cli/src/engine/engine.ts
new file mode 100644
index 00000000..644fa7df
--- /dev/null
+++ b/packages/cli/src/engine/engine.ts
@@ -0,0 +1,123 @@
+/**
+ * The one contract between the TUI / output modes and where the agent runs.
+ *
+ * Every mode (local, cloud, device:) speaks the same HugAgentOS HTTP + SSE API,
+ * so events reach the UI unchanged: see
+ * .claude/skills/hugagent-frontend-dev/references/sse-events.md for every event type.
+ * The UI depends on this file only, never on a concrete engine.
+ */
+import type { ChatEvent, ChatHistoryMessage, FileDecision, QuestionAnswer } from '@hugagent/chat-sdk';
+
+export type { ChatEvent, ChatHistoryMessage, FileDecision, QuestionAnswer };
+
+/** Where the agent runs. `local` works in the current directory on this machine. */
+export type Mode = 'local' | 'cloud' | `device:${string}`;
+
+export interface EngineInfo {
+ mode: Mode;
+ /** Short status-bar text, e.g. "local · ~/repo" or "cloud · agent.example.com". */
+ label: string;
+ /** Base URL the engine talks to (local: loopback engine). */
+ server: string;
+ /** Absolute directory the agent works in; set only when it can reach this machine's files. */
+ workspace?: string;
+ /** The project new sessions are created in (local: the project bound to `workspace`). */
+ projectId?: string;
+ /** False for cloud and device modes: the agent cannot read or change local files. */
+ canAccessCwd: boolean;
+}
+
+export interface SessionSummary {
+ chatId: string;
+ title: string;
+ updatedAt?: string;
+ projectId?: string;
+}
+
+export interface ModelOption {
+ providerId: string;
+ modelName: string;
+ label: string;
+ current?: boolean;
+}
+
+/** steer: apply inside the running turn; followUp: right after it; nextRun: as the next message. */
+export type DeliveryMode = 'steer' | 'followUp' | 'nextRun';
+
+/** Tool execution preset, as served by /v1/tool-approval. */
+export type PermissionMode = 'ask' | 'auto' | 'full';
+
+export interface SendOptions {
+ /** Per-message model override (ChatRequest.model_provider_id / model_name). */
+ model?: { providerId: string; modelName: string };
+ attachments?: { file_id: string; name: string; mime_type: string }[];
+ signal?: AbortSignal;
+}
+
+/** What a manual compaction did; `compacted` is false when nothing was old enough to fold. */
+export interface CompactSummary {
+ compacted: boolean;
+ tokensBefore: number | null;
+ tokensAfter: number | null;
+}
+
+export interface ActiveRun {
+ runId: string;
+ /** Last delivered event offset, for resume(). */
+ offset?: number;
+}
+
+export interface Engine {
+ readonly info: EngineInfo;
+
+ /** Start or attach to the backend and bind the workspace. Idempotent. */
+ connect(): Promise;
+ /** Release the connection; never stops a local engine other clients may share. */
+ close(): Promise;
+
+ newSession(title?: string): Promise;
+ /** Newest first; local mode lists only the sessions of the bound project. */
+ listSessions(limit?: number): Promise;
+ /** Oldest first, at most `limit` latest messages. */
+ history(chatId: string, limit?: number): Promise;
+ /** Branch a session (POST /v1/chats/{id}/fork); returns the new session. */
+ fork(chatId: string, messageId?: string): Promise;
+
+ /** Start a turn and stream its events until the run ends (`end` / [DONE]). */
+ send(chatId: string, message: string, options?: SendOptions): AsyncIterable;
+ /** Re-follow a run from an event offset (reconnect, or after /resume). */
+ resume(runId: string, fromOffset?: number, signal?: AbortSignal): AsyncIterable;
+ activeRun(chatId: string): Promise;
+ cancel(runId: string): Promise;
+ steer(runId: string, message: string, mode?: DeliveryMode): Promise;
+
+ /** Answer a `file_confirm` event (file writes, commands, tool permissions all use it). */
+ confirm(chatId: string, confirmId: string, decision: FileDecision, optionId?: string): Promise;
+ /** Answer a `user_question` event. */
+ answer(chatId: string, requestId: string, answers: QuestionAnswer[]): Promise;
+
+ /** Compact a session's context now (POST /v1/chats/{id}/compact). */
+ compact(chatId: string): Promise;
+
+ listModels(): Promise;
+ getPermissionMode(): Promise;
+ setPermissionMode(mode: PermissionMode): Promise;
+}
+
+/** Builds the engine for a mode; implemented in engine/index.ts. */
+export type EngineFactory = (mode: Mode, options: { cwd: string }) => Promise;
+
+/** Options every output mode receives from main.ts. */
+export interface RunOptions {
+ engine: Engine;
+ /** Create engines for /mode switches. */
+ createEngine: EngineFactory;
+ cwd: string;
+ /** Session to continue (-c / -r / --session). */
+ chatId?: string;
+ model?: { providerId: string; modelName: string };
+ /** `hugagent [prompt]`: send this as the first message once the UI is up. */
+ prompt?: string;
+ /** `-r` without an id: let the user pick a session of this engine to resume. */
+ pickSession?: boolean;
+}
diff --git a/packages/cli/src/engine/errors.ts b/packages/cli/src/engine/errors.ts
new file mode 100644
index 00000000..3ee6375f
--- /dev/null
+++ b/packages/cli/src/engine/errors.ts
@@ -0,0 +1,47 @@
+/** Errors the engine layer raises; `exitCode` is what main.ts exits with when one reaches the top. */
+
+export const EXIT = { ok: 0, failed: 1, usage: 2, setup: 3, interrupted: 130 } as const;
+
+export class CliError extends Error {
+ constructor(message: string, public readonly exitCode: number = EXIT.failed) {
+ super(message);
+ this.name = 'CliError';
+ }
+}
+
+/** An HTTP call to HugAgentOS failed; `detail` is the server's error body, kept for display. */
+export class HugAgentOSHttpError extends CliError {
+ constructor(
+ message: string,
+ public readonly status: number,
+ public readonly code?: number,
+ public readonly detail?: unknown,
+ ) {
+ super(message, EXIT.failed);
+ this.name = 'HugAgentOSHttpError';
+ }
+}
+
+/** The local engine or cloud login needs a one-time setup step before anything works. */
+export class SetupRequiredError extends CliError {
+ constructor(message: string) {
+ super(message, EXIT.setup);
+ this.name = 'SetupRequiredError';
+ }
+}
+
+/** The engine for this mode cannot do what was asked (device mode lacks most of the API). */
+export class UnsupportedError extends CliError {
+ constructor(message: string) {
+ super(message, EXIT.failed);
+ this.name = 'UnsupportedError';
+ }
+}
+
+export function isAbortError(error: unknown): boolean {
+ return error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError');
+}
+
+export function errorMessage(error: unknown): string {
+ return error instanceof Error ? error.message : String(error);
+}
diff --git a/packages/cli/src/engine/headless.ts b/packages/cli/src/engine/headless.ts
new file mode 100644
index 00000000..5c31ae67
--- /dev/null
+++ b/packages/cli/src/engine/headless.ts
@@ -0,0 +1,199 @@
+/**
+ * One non-interactive turn, shared by the text and JSON output modes: picks the session, applies the
+ * non-interactive approval policy (DESIGN.md §8.4: deny file_confirm unless --auto-approve, skip
+ * every user_question) and turns Ctrl+C into a run cancel.
+ */
+import type { ChatEvent, Engine, FileDecision, QuestionAnswer } from './engine.js';
+import { CliError, EXIT, errorMessage } from './errors.js';
+
+export type TurnStatus = 'completed' | 'failed' | 'cancelled';
+
+/** What main.ts hands the text and JSON modes. The engine may not be connected yet. */
+export interface HeadlessOptions {
+ engine: Engine;
+ prompt: string;
+ chatId?: string;
+ continueLast?: boolean;
+ model?: { providerId: string; modelName: string };
+ autoApprove: boolean;
+ stdout?: NodeJS.WritableStream;
+ stderr?: NodeJS.WritableStream;
+}
+
+export interface TurnResult {
+ status: TurnStatus;
+ chatId: string;
+ runId?: string;
+ error?: string;
+}
+
+export interface TurnOptions {
+ autoApprove: boolean;
+ model?: { providerId: string; modelName: string };
+ signal?: AbortSignal;
+ onEvent(event: ChatEvent): void;
+ /** One line about what the policy answered on the user's behalf. */
+ onNote?(note: string): void;
+ onRunId?(runId: string): void;
+}
+
+/** How one headless turn ended; exactly one of `result` and `error` is set. */
+export interface HeadlessOutcome {
+ chatId?: string;
+ result?: TurnResult;
+ /** Thrown while connecting, choosing the session or running the turn. */
+ error?: unknown;
+ interrupted: boolean;
+}
+
+/** Connect, choose the session and run one turn with Ctrl+C wired to cancel: the spine of `-p` and `--output json`. */
+export async function runHeadless(
+ options: HeadlessOptions,
+ hooks: Pick,
+): Promise {
+ let chatId: string | undefined;
+ const interrupt = new Interrupt(options.engine, () => chatId);
+ try {
+ await options.engine.connect();
+ chatId = await chooseChat(options.engine, options.chatId, !!options.continueLast);
+ const result = await runTurn(options.engine, chatId, options.prompt, {
+ autoApprove: options.autoApprove,
+ model: options.model,
+ signal: interrupt.signal,
+ onRunId: (runId) => { interrupt.runId = runId; },
+ ...hooks,
+ });
+ return { chatId, result, interrupted: interrupt.interrupted };
+ } catch (error) {
+ return { chatId, error, interrupted: interrupt.interrupted };
+ } finally {
+ await interrupt.dispose();
+ }
+}
+
+/** Exit code per DESIGN.md §8.4: 0 completed, 130 interrupted, the CliError's own code, else 1. */
+export function headlessExitCode(outcome: HeadlessOutcome): number {
+ if (outcome.result) {
+ if (outcome.result.status === 'completed') return EXIT.ok;
+ return outcome.result.status === 'cancelled' && outcome.interrupted ? EXIT.interrupted : EXIT.failed;
+ }
+ if (outcome.interrupted) return EXIT.interrupted;
+ return outcome.error instanceof CliError ? outcome.error.exitCode : EXIT.failed;
+}
+
+/** -r wins, then -c (latest session of this engine), else a new session. */
+export async function chooseChat(engine: Engine, chatId: string | undefined, continueLast: boolean): Promise {
+ if (chatId) return chatId;
+ if (continueLast) {
+ const [latest] = await engine.listSessions(1);
+ if (latest) return latest.chatId;
+ }
+ return (await engine.newSession()).chatId;
+}
+
+export function eventError(event: ChatEvent): string {
+ const value = event.error ?? event.message ?? event.content;
+ if (typeof value === 'string' && value) return value;
+ if (value && typeof value === 'object') {
+ const nested = (value as Record).message;
+ return typeof nested === 'string' ? nested : JSON.stringify(value);
+ }
+ return 'Run failed';
+}
+
+export async function runTurn(engine: Engine, chatId: string, message: string, options: TurnOptions): Promise {
+ const result: TurnResult = { status: 'completed', chatId };
+ let failure: string | undefined;
+ let endStatus: string | undefined;
+ try {
+ for await (const event of engine.send(chatId, message, { model: options.model, signal: options.signal })) {
+ if (event.type === 'run_started' && typeof event.run_id === 'string' && !result.runId) {
+ result.runId = event.run_id;
+ options.onRunId?.(event.run_id);
+ }
+ options.onEvent(event);
+ if (event.type === 'error') failure ??= eventError(event);
+ else if (event.type === 'end' && typeof event.status === 'string') endStatus = event.status;
+ else if (event.type === 'file_confirm' && typeof event.confirm_id === 'string' && !event.expired) {
+ const decision: FileDecision = options.autoApprove ? 'allow' : 'deny';
+ await engine.confirm(chatId, event.confirm_id, decision).then(
+ () => options.onNote?.(`${decision === 'allow' ? 'approved' : 'denied'} ${describeConfirm(event)}` +
+ (decision === 'deny' ? ' (pass --auto-approve to allow)' : '')),
+ (error: unknown) => options.onNote?.(`could not answer approval ${event.confirm_id}: ${errorMessage(error)}`),
+ );
+ } else if (event.type === 'user_question' && typeof event.request_id === 'string') {
+ const questions = Array.isArray(event.questions) ? event.questions : [];
+ const answers: QuestionAnswer[] = questions
+ .map((question) => (question && typeof question === 'object' ? (question as Record).id : undefined))
+ .filter((id): id is string => typeof id === 'string')
+ .map((id) => ({ id, selected: [], skipped: true }));
+ if (answers.length) {
+ await engine.answer(chatId, event.request_id, answers).then(
+ () => options.onNote?.(`skipped ${answers.length} question(s) from the agent (non-interactive)`),
+ (error: unknown) => options.onNote?.(`could not skip question ${event.request_id}: ${errorMessage(error)}`),
+ );
+ }
+ }
+ }
+ } catch (error) {
+ if (options.signal?.aborted) return { ...result, status: 'cancelled' };
+ return { ...result, status: 'failed', error: errorMessage(error) };
+ }
+ if (options.signal?.aborted || endStatus === 'cancelled') return { ...result, status: 'cancelled' };
+ if (failure || endStatus === 'failed' || endStatus === 'needs_attention') {
+ return { ...result, status: 'failed', error: failure ?? `Run ${endStatus}` };
+ }
+ return result;
+}
+
+function describeConfirm(event: ChatEvent): string {
+ for (const key of ['title', 'summary', 'description', 'path', 'file_path', 'command', 'tool_name', 'action']) {
+ const value = event[key];
+ if (typeof value === 'string' && value.trim()) return value.trim().slice(0, 160);
+ }
+ return 'a pending action';
+}
+
+/**
+ * Ctrl+C during a turn: abort following, cancel the run on the server, then let the mode exit 130.
+ * A second Ctrl+C exits at once.
+ */
+export class Interrupt {
+ readonly controller = new AbortController();
+ runId?: string;
+ private cancelling?: Promise;
+
+ constructor(private readonly engine: Engine, private readonly chatId: () => string | undefined) {
+ process.on('SIGINT', this.handle);
+ }
+
+ get signal(): AbortSignal {
+ return this.controller.signal;
+ }
+
+ get interrupted(): boolean {
+ return this.controller.signal.aborted;
+ }
+
+ private readonly handle = () => {
+ if (this.cancelling) process.exit(130);
+ this.controller.abort();
+ this.cancelling = this.cancelRun();
+ };
+
+ private async cancelRun(): Promise {
+ const work = (async () => {
+ let runId = this.runId;
+ const chatId = this.chatId();
+ if (!runId && chatId) runId = (await this.engine.activeRun(chatId).catch(() => null))?.runId;
+ if (runId) await this.engine.cancel(runId);
+ })().catch(() => {});
+ await Promise.race([work, new Promise((resolve) => setTimeout(resolve, 5000).unref())]);
+ }
+
+ /** Wait for the cancel request to go out, and stop listening for Ctrl+C. */
+ async dispose(): Promise {
+ process.off('SIGINT', this.handle);
+ await this.cancelling;
+ }
+}
diff --git a/packages/cli/src/engine/http.ts b/packages/cli/src/engine/http.ts
new file mode 100644
index 00000000..188e96a2
--- /dev/null
+++ b/packages/cli/src/engine/http.ts
@@ -0,0 +1,303 @@
+/**
+ * One authenticated HTTP layer for every engine: `ChatClient` from @hugagent/chat-sdk for the
+ * streaming and confirmation calls, plus the HugAgentOS endpoints the SDK does not cover.
+ * Every JSON call unwraps the `{code, data}` envelope; failures become HugAgentOSHttpError with
+ * the HTTP status and the server's own message.
+ */
+import { randomUUID } from 'node:crypto';
+import { ChatApiError, ChatClient, type ChatEvent, type ChatHistoryMessage, type ChatRequest, type ChatSession,
+ type FileDecision, type QuestionAnswer } from '@hugagent/chat-sdk';
+import type { DeliveryMode, PermissionMode } from './engine.js';
+import { HugAgentOSHttpError } from './errors.js';
+
+const JSON_TIMEOUT_MS = 30_000;
+
+export interface HttpOptions {
+ /** API root, e.g. http://127.0.0.1:8123 or https://agent.example.com/api. */
+ baseUrl: string;
+ token: string;
+ fetch?: typeof fetch;
+}
+
+export interface LocalProject { project_id: string; name: string; local_path: string; created: boolean }
+export interface CompactResult { compacted: boolean; tokens_before: number | null; tokens_after: number | null }
+export interface RunStatus { run_id: string; chat_id: string; message_id?: string; status: string }
+export interface ActiveRunInfo { run_id: string; message_id?: string; status?: string; last_event_offset?: number }
+export interface SelectableModel {
+ provider_id: string;
+ display_name: string;
+ model_name: string;
+ provider?: string;
+ is_default?: boolean;
+}
+export interface ModelCapabilities {
+ main_agent?: Record;
+ user_model_switch?: { enabled?: boolean; models?: SelectableModel[] };
+ [key: string]: unknown;
+}
+export interface Page { items: T[]; [key: string]: unknown }
+/** A session-manager operation receipt (personal_agent/sessions/session_queue.py `public`). */
+export interface OperationJob {
+ id: string;
+ target?: string;
+ operation?: string;
+ status: string;
+ result?: unknown;
+ deadline?: number;
+ [key: string]: unknown;
+}
+
+/** Turns any error body HugAgentOS produces into one readable line. */
+export function describeErrorBody(status: number, body: unknown): { message: string; code?: number; detail?: unknown } {
+ const fallback = { message: 'HTTP ' + status };
+ if (!body || typeof body !== 'object') return fallback;
+ const record = body as Record;
+ const code = typeof record.code === 'number' ? record.code : undefined;
+ // error_response(): {code, message, data}
+ if (typeof record.message === 'string' && record.message) {
+ return { message: record.message, code, detail: record.data ?? record.detail };
+ }
+ const detail = record.detail;
+ if (typeof detail === 'string' && detail) return { message: detail, code, detail };
+ if (Array.isArray(detail)) {
+ // FastAPI validation: [{loc: [...], msg}]
+ const parts = detail.map((item) => {
+ const entry = item && typeof item === 'object' ? item as Record : {};
+ const loc = Array.isArray(entry.loc) ? entry.loc.filter((part) => part !== 'body').join('.') : '';
+ const msg = typeof entry.msg === 'string' ? entry.msg : JSON.stringify(item);
+ return loc ? loc + ': ' + msg : msg;
+ });
+ return { message: parts.join('; ') || fallback.message, code, detail };
+ }
+ if (detail && typeof detail === 'object') {
+ const nested = detail as Record;
+ const message = typeof nested.message === 'string' ? nested.message
+ : typeof nested.error === 'string' ? nested.error : JSON.stringify(detail);
+ return { message, code: typeof nested.code === 'number' ? nested.code : code, detail };
+ }
+ if (typeof record.error === 'string') return { message: record.error, code, detail: record };
+ return fallback;
+}
+
+function httpError(status: number, body: unknown): HugAgentOSHttpError {
+ const { message, code, detail } = describeErrorBody(status, body);
+ return new HugAgentOSHttpError(message + ' (HTTP ' + status + ')', status, code, detail);
+}
+
+/** ChatClient only reads `message`; rebuild the full description from what it kept. */
+function translate(error: unknown): unknown {
+ if (!(error instanceof ChatApiError)) return error;
+ const body = /^HTTP \d+$/.test(error.message) ? { detail: error.detail } : { message: error.message, detail: error.detail };
+ const { message, code, detail } = describeErrorBody(error.status, body);
+ // An envelope code the SDK kept wins, with the detail it came with.
+ const known = error.code !== undefined;
+ return new HugAgentOSHttpError(message + ' (HTTP ' + error.status + ')', error.status,
+ known ? error.code : code, known ? error.detail : detail);
+}
+
+/** An SDK call with its error translated like every other call here. */
+function viaSdk(call: Promise): Promise {
+ return call.catch((error: unknown) => { throw translate(error); });
+}
+
+async function* translated(source: AsyncIterable): AsyncGenerator {
+ try {
+ yield* source;
+ } catch (error) {
+ throw translate(error);
+ }
+}
+
+const seg = (value: string) => {
+ if (!value || !value.trim()) throw new Error('Identifier must not be empty');
+ return encodeURIComponent(value);
+};
+
+export class HugAgentOSHttp {
+ readonly baseUrl: string;
+ readonly chat: ChatClient;
+ private readonly fetcher: typeof fetch;
+
+ constructor(private readonly options: HttpOptions) {
+ this.baseUrl = options.baseUrl.replace(/\/+$/, '');
+ const base = options.fetch ?? globalThis.fetch.bind(globalThis);
+ // Credentials never follow a redirect to another host.
+ this.fetcher = ((input: RequestInfo | URL, init?: RequestInit) => base(input, { ...init, redirect: 'error' })) as typeof fetch;
+ this.chat = new ChatClient({
+ baseUrl: this.baseUrl,
+ headers: () => this.headers(),
+ credentials: 'omit',
+ fetch: this.fetcher,
+ });
+ }
+
+ private headers(): Record {
+ return { Authorization: 'Bearer ' + this.options.token, Accept: 'application/json' };
+ }
+
+ /** One JSON call: envelope unwrapped, errors carry status + server detail. */
+ async json(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise {
+ const timeout = AbortSignal.timeout(JSON_TIMEOUT_MS);
+ const headers: Record = this.headers();
+ if (body !== undefined) headers['Content-Type'] = 'application/json';
+ let response: Response;
+ try {
+ response = await this.fetcher(this.baseUrl + path, {
+ method,
+ headers,
+ body: body === undefined ? undefined : JSON.stringify(body),
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
+ });
+ } catch (error) {
+ if (signal?.aborted) throw error;
+ if (timeout.aborted) throw new HugAgentOSHttpError(`${method} ${path} timed out`, 0);
+ throw new HugAgentOSHttpError(`Cannot reach ${this.baseUrl}: ${causeOf(error)}`, 0);
+ }
+ const text = await response.text();
+ let parsed: unknown = undefined;
+ if (text) {
+ try {
+ parsed = JSON.parse(text);
+ } catch {
+ if (!response.ok) throw new HugAgentOSHttpError(`${text.slice(0, 200)} (HTTP ${response.status})`, response.status);
+ throw new HugAgentOSHttpError(`Invalid JSON from ${method} ${path} (HTTP ${response.status})`, response.status);
+ }
+ }
+ if (!response.ok) throw httpError(response.status, parsed);
+ if (parsed === undefined) return undefined as T;
+ if (parsed && typeof parsed === 'object' && 'code' in parsed && 'data' in parsed) {
+ const envelope = parsed as { code: unknown; data: T; message?: string };
+ if (typeof envelope.code === 'number' && (envelope.code < 10000 || envelope.code >= 20000)) {
+ throw new HugAgentOSHttpError((envelope.message || 'API request failed') + ' (code ' + envelope.code + ')',
+ response.status, envelope.code, envelope.data);
+ }
+ return envelope.data;
+ }
+ return parsed as T;
+ }
+
+ /** GET /health on the server root, never throws. */
+ async health(timeoutMs = 3000): Promise {
+ try {
+ const response = await this.fetcher(this.baseUrl + '/health', { signal: AbortSignal.timeout(timeoutMs) });
+ await response.body?.cancel().catch(() => {});
+ return response.status === 200;
+ } catch {
+ return false;
+ }
+ }
+
+ // ── Sessions ────────────────────────────────────────────────────────────
+
+ createChat(data: { title?: string; projectId?: string }, signal?: AbortSignal) {
+ const body: Record = {};
+ if (data.title) body.title = data.title;
+ if (data.projectId) body.project_id = data.projectId;
+ return this.json('POST', '/v1/chats', body, signal);
+ }
+
+ listChats(page = 1, pageSize = 20, signal?: AbortSignal) {
+ return this.json>('GET', '/v1/chats?' + query({ page, page_size: pageSize, exclude_automation: true }), undefined, signal);
+ }
+
+ listProjectChats(projectId: string, page = 1, pageSize = 20, signal?: AbortSignal) {
+ return this.json>('GET', `/v1/projects/${seg(projectId)}/chats?` + query({ page, page_size: pageSize, scope: 'mine' }), undefined, signal);
+ }
+
+ /** `order=desc` makes page 1 the latest messages. */
+ messages(chatId: string, page = 1, pageSize = 50, order: 'asc' | 'desc' = 'desc', signal?: AbortSignal) {
+ return this.json>('GET', `/v1/chats/${seg(chatId)}/messages?` + query({ page, page_size: pageSize, order }), undefined, signal);
+ }
+
+ fork(chatId: string, throughMessageId?: string) {
+ const body: Record = { request_id: randomUUID() };
+ if (throughMessageId) body.through_message_id = throughMessageId;
+ return this.json('POST', `/v1/chats/${seg(chatId)}/fork`, body);
+ }
+
+ compact(chatId: string) {
+ return this.json('POST', `/v1/chats/${seg(chatId)}/compact`, {});
+ }
+
+ /** Bind a folder to its local project, creating it, and grant it to the OS sandbox. */
+ resolveLocalProject(path: string) {
+ return this.json('POST', '/v1/projects/local/resolve', { path, create: true, grant: true });
+ }
+
+ // ── Runs ────────────────────────────────────────────────────────────────
+
+ send(request: ChatRequest, signal?: AbortSignal): AsyncGenerator {
+ return translated(this.chat.send(request, signal));
+ }
+
+ resume(runId: string, from = 0, signal?: AbortSignal): AsyncGenerator {
+ return translated(this.chat.resume(runId, from, signal));
+ }
+
+ activeRun(chatId: string, signal?: AbortSignal): Promise {
+ return viaSdk(this.chat.activeRun(chatId, signal)) as Promise;
+ }
+
+ async cancel(runId: string): Promise {
+ await viaSdk(this.chat.cancel(runId));
+ }
+
+ /** Each steer gets a fresh steer_id; the server de-duplicates retries by it. */
+ steer(runId: string, message: string, mode: DeliveryMode = 'steer') {
+ return this.json>('POST', `/v1/chat-runs/${seg(runId)}/steer`, {
+ steer_id: randomUUID().replace(/-/g, ''), message, delivery_mode: mode,
+ });
+ }
+
+ confirmFile(chatId: string, confirmId: string, decision: FileDecision, optionId?: string) {
+ return viaSdk(this.chat.confirmFile(chatId, confirmId, decision, optionId));
+ }
+
+ answerQuestion(chatId: string, requestId: string, answers: QuestionAnswer[]) {
+ return viaSdk(this.chat.answerQuestion(chatId, requestId, answers));
+ }
+
+ // ── Settings ────────────────────────────────────────────────────────────
+
+ /** The web model picker's source (frontend api.ts getMainModelCapabilities). */
+ modelCapabilities() {
+ return this.json('GET', '/v1/models/capabilities');
+ }
+
+ async getToolApproval(): Promise {
+ return (await this.json<{ mode: PermissionMode }>('GET', '/v1/tool-approval')).mode;
+ }
+
+ async setToolApproval(mode: PermissionMode): Promise {
+ return (await this.json<{ mode: PermissionMode }>('PUT', '/v1/tool-approval', { mode })).mode;
+ }
+
+ // ── Session-manager queue (device:) ─────────────────────────────────
+
+ submitOperation(target: string, operation: string, args: Record) {
+ return this.json('POST', '/v1/personal-agent/session-manager/operations', {
+ target, operation, args, request_key: randomUUID().replace(/-/g, ''),
+ });
+ }
+
+ getOperation(id: string, signal?: AbortSignal) {
+ return this.json('GET', `/v1/personal-agent/session-manager/operations/${seg(id)}`, undefined, signal);
+ }
+
+ listDevices() {
+ return this.json('GET', '/v1/personal-agent/session-manager/devices');
+ }
+}
+
+function query(values: Record): string {
+ return new URLSearchParams(Object.entries(values).map(([key, value]) => [key, String(value)])).toString();
+}
+
+function causeOf(error: unknown): string {
+ if (error instanceof Error) {
+ const cause = (error as { cause?: unknown }).cause;
+ if (cause instanceof Error) return cause.message;
+ return error.message;
+ }
+ return String(error);
+}
diff --git a/packages/cli/src/engine/index.ts b/packages/cli/src/engine/index.ts
new file mode 100644
index 00000000..e19175a8
--- /dev/null
+++ b/packages/cli/src/engine/index.ts
@@ -0,0 +1,33 @@
+/** The EngineFactory: builds and connects the engine for a mode. */
+import { resolveCloudCredentials } from '../config.js';
+import type { Engine, EngineFactory, Mode } from './engine.js';
+import { LocalEngine } from './local.js';
+import { DeviceEngine, RemoteEngine } from './remote.js';
+
+export interface FactoryDefaults {
+ /** --server: the cloud server for cloud and device modes. */
+ server?: string;
+ env?: NodeJS.ProcessEnv;
+ /** Progress text while a local engine starts (only shown by callers that want it). */
+ onStatus?: (message: string) => void;
+}
+
+export function buildEngine(mode: Mode, options: { cwd: string }, defaults: FactoryDefaults = {}): Engine {
+ const env = defaults.env ?? process.env;
+ if (mode === 'local') return new LocalEngine({ cwd: options.cwd, env, onStatus: defaults.onStatus });
+ const credentials = resolveCloudCredentials(defaults.server, env);
+ return mode === 'cloud' ? new RemoteEngine(credentials) : new DeviceEngine(mode, credentials);
+}
+
+/** Engines come back connected (connect() is idempotent, so calling it again is harmless). */
+export function engineFactory(defaults: FactoryDefaults = {}): EngineFactory {
+ return async (mode, options) => {
+ const engine = buildEngine(mode, options, defaults);
+ await engine.connect();
+ return engine;
+ };
+}
+
+export { LocalEngine } from './local.js';
+export { DeviceEngine, RemoteEngine } from './remote.js';
+export * from './errors.js';
diff --git a/packages/cli/src/engine/local.ts b/packages/cli/src/engine/local.ts
new file mode 100644
index 00000000..92b2adc1
--- /dev/null
+++ b/packages/cli/src/engine/local.ts
@@ -0,0 +1,192 @@
+/**
+ * LocalEngine: attach to (or start) the shared headless engine on this machine and bind the
+ * current directory to a backend local project (DESIGN.md §8.1, §8.2).
+ */
+import { spawn } from 'node:child_process';
+import { closeSync, mkdirSync, openSync, readFileSync, realpathSync } from 'node:fs';
+import { homedir } from 'node:os';
+import { join } from 'node:path';
+import { hugagentHome } from '../config.js';
+import { HttpEngine, latestSummaries, pageSizeFor, toSummary } from './base.js';
+import type { EngineInfo, SessionSummary } from './engine.js';
+import { CliError, EXIT, HugAgentOSHttpError, SetupRequiredError } from './errors.js';
+import { HugAgentOSHttp } from './http.js';
+import { resolveServerCommand } from './server-command.js';
+
+export interface ReadyFile {
+ version: number;
+ url: string;
+ token: string;
+ pid: number;
+ started_at?: string;
+ home?: string;
+}
+
+export interface LocalEngineOptions {
+ cwd: string;
+ env?: NodeJS.ProcessEnv;
+ /** How long a fresh engine may take to write its ready file (default 120 s). */
+ startTimeoutMs?: number;
+ /** Progress lines while starting the engine ("Starting local engine…"). */
+ onStatus?: (message: string) => void;
+}
+
+export function readyFilePath(env: NodeJS.ProcessEnv = process.env): string {
+ return join(hugagentHome(env), 'engine.json');
+}
+
+export function engineLogPath(env: NodeJS.ProcessEnv = process.env): string {
+ return join(hugagentHome(env), 'logs', 'engine.log');
+}
+
+export function readReadyFile(path: string): ReadyFile | null {
+ let parsed: unknown;
+ try {
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
+ } catch {
+ return null;
+ }
+ if (!parsed || typeof parsed !== 'object') return null;
+ const ready = parsed as Partial;
+ if (typeof ready.url !== 'string' || typeof ready.token !== 'string' || !ready.token ||
+ typeof ready.pid !== 'number' || !Number.isInteger(ready.pid) || ready.pid <= 0) return null;
+ try {
+ const url = new URL(ready.url);
+ // The engine only ever binds loopback; anything else is not ours to send the token to.
+ if (url.protocol !== 'http:' || !['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)) return null;
+ } catch {
+ return null;
+ }
+ return ready as ReadyFile;
+}
+
+export function pidAlive(pid: number): boolean {
+ try {
+ process.kill(pid, 0);
+ return true;
+ } catch (error) {
+ return (error as NodeJS.ErrnoException).code === 'EPERM';
+ }
+}
+
+async function usable(ready: ReadyFile): Promise {
+ if (!pidAlive(ready.pid)) return false;
+ const http = new HugAgentOSHttp({ baseUrl: ready.url, token: ready.token });
+ for (let attempt = 0; attempt < 3; attempt++) {
+ if (await http.health()) return true;
+ await new Promise((resolve) => setTimeout(resolve, 300));
+ }
+ return false;
+}
+
+function logTail(path: string, lines = 15): string {
+ try {
+ return readFileSync(path, 'utf8').trimEnd().split('\n').slice(-lines).join('\n');
+ } catch {
+ return '';
+ }
+}
+
+/**
+ * Reuse the running engine, or start one detached and wait for its ready file. Concurrent starts
+ * are safe without a lock here: `hugagent-server engine` holds its own per-home lock, and a second
+ * one waits for the first and exits 0 once the ready file describes it.
+ */
+export async function discoverEngine(options: Omit = {}): Promise {
+ const env = options.env ?? process.env;
+ const readyPath = readyFilePath(env);
+ const existing = readReadyFile(readyPath);
+ if (existing && await usable(existing)) return existing;
+
+ const home = hugagentHome(env);
+ mkdirSync(join(home, 'logs'), { recursive: true, mode: 0o700 });
+ const timeoutMs = options.startTimeoutMs ?? 120_000;
+ const logPath = engineLogPath(env);
+ const command = resolveServerCommand(env);
+ options.onStatus?.('Starting local engine…');
+ let exit: { code: number | null; signal: NodeJS.Signals | null; error?: Error } | undefined;
+ const log = openSync(logPath, 'a', 0o600);
+ try {
+ const child = spawn(command[0], [...command.slice(1), 'engine', '--ready-file', readyPath], {
+ cwd: home,
+ env,
+ detached: true,
+ stdio: ['ignore', log, log],
+ windowsHide: true,
+ });
+ child.on('exit', (code, signal) => { exit ??= { code, signal }; });
+ child.on('error', (error) => { exit = { code: null, signal: null, error }; });
+ child.unref();
+ } finally {
+ closeSync(log);
+ }
+ const deadline = Date.now() + timeoutMs;
+ while (Date.now() < deadline) {
+ const ready = readReadyFile(readyPath);
+ if (ready && await usable(ready)) return ready;
+ const stopped = exit as typeof exit;
+ if (stopped) {
+ if (stopped.code === EXIT.setup) {
+ throw new SetupRequiredError('HugAgentOS is not initialized on this machine: run `hugagent onboard --no-serve` first');
+ }
+ const reason = stopped.error ? stopped.error.message : stopped.signal ? `signal ${stopped.signal}` : `exit code ${stopped.code}`;
+ const tail = logTail(logPath);
+ throw new CliError(`Local engine stopped before it was ready (${reason}); see ${logPath}${tail ? '\n' + tail : ''}`);
+ }
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ }
+ throw new CliError(`Local engine did not become ready within ${Math.round(timeoutMs / 1000)} s; see ${logPath}`);
+}
+
+export function tildify(path: string): string {
+ const home = homedir();
+ return path === home ? '~' : path.startsWith(home + '/') ? '~' + path.slice(home.length) : path;
+}
+
+export class LocalEngine extends HttpEngine {
+ readonly info: EngineInfo;
+
+ constructor(private readonly options: LocalEngineOptions) {
+ super();
+ this.info = { mode: 'local', label: 'local · ' + tildify(options.cwd), server: '', workspace: options.cwd, canAccessCwd: true };
+ }
+
+ protected async open(): Promise