Phase 005: vendor ArkScope, land the Arco style baseline, and fix bridge interpreter resolution

- Vendor ArkScope decorative layers (ScrambleTitle / IsometricWave) with provenance notes; decorative-only per AGENTS.md red lines
- Migrate theme tokens to Arco, register the Arco theme, and add hook adapter composables (useMessage / useDialog / useNotification) with matching signatures
- Sample pages: LoginView and AppSidebar on the new baseline; docs/arco-mapping.md and docs/ui-baseline.md land
- agent-bridge: prefer the PM-selected venv from facts.json over legacy in-tree venvs (legacy lacks the mcp extra, so every MCP server showed 0 tools / not connected)
- agent-bridge python (vendored, deliberate deviation): MCP discovery import fallback and pyyaml -> hermes_yaml so plugins and MCP work under the PM env; install the mcp extra via `hermes pm install --extra mcp`
- Chat-chain fragment: docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md

Verified: harness:check, test (2020 passed), test:e2e (30 passed), build all green; manual: plugins 100/0 warnings, MCP 6/6 servers connected with 104 tools.
This commit is contained in:
2026-10-17 09:45:30 +08:00
parent 4701bf0ad0
commit 4baccd5d88
106 changed files with 1493 additions and 221 deletions
+49 -42
View File
@@ -4,6 +4,8 @@ import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { setApiKey, hasApiKey } from "@/api/client";
import { fetchAuthStatus, loginWithPassword } from "@/api/auth";
import ScrambleTitle from "@/vendor/arkscore/ScrambleTitle/index.vue";
import IsometricWave from "@/vendor/arkscore/IsometricWave/index.vue";
const { t } = useI18n();
const router = useRouter();
@@ -13,12 +15,16 @@ const password = ref("");
const loading = ref(false);
const errorMsg = ref("");
const showLockResetHint = ref(false);
// Arco's a-input sets inheritAttrs: false (no autofocus passthrough),
// so focus the input explicitly on mount.
const usernameInput = ref<{ focus: () => void } | null>(null);
if (hasApiKey()) {
router.replace("/hermes/chat");
}
onMounted(async () => {
usernameInput.value?.focus();
try {
await fetchAuthStatus();
} catch {
@@ -59,26 +65,33 @@ async function handlePasswordLogin() {
<template>
<div class="login-view">
<!-- 装饰位面:只承载 ArkScope 动画,不承载信息/交互 -->
<div class="login-decor" aria-hidden="true">
<IsometricWave />
</div>
<div class="login-card">
<div class="login-logo">
<img src="/logo.png" alt="Hermes" width="80" height="80" />
</div>
<h1 class="login-title">{{ t("login.title") }}</h1>
<h1 class="login-title">
<ScrambleTitle :content="t('login.title')" />
</h1>
<p class="login-desc">{{ t("login.description") }}</p>
<p class="login-default-hint">{{ t("login.defaultCredentialsHint") }}</p>
<form class="login-form" @submit.prevent="handleLogin">
<input
<a-input
v-model="username"
type="text"
ref="usernameInput"
class="login-input"
size="large"
:placeholder="t('login.usernamePlaceholder')"
autofocus
/>
<input
<a-input
v-model="password"
type="password"
class="login-input"
size="large"
type="password"
:placeholder="t('login.passwordPlaceholder')"
@keyup.enter="handleLogin"
/>
@@ -90,9 +103,16 @@ async function handlePasswordLogin() {
<span>{{ t("login.defaultLoginResetHint") }}</span>
<code>hermes-ui reset-default-login</code>
</div>
<button type="submit" class="login-btn" :disabled="loading">
<a-button
html-type="submit"
type="primary"
size="large"
long
class="login-btn"
:loading="loading"
>
{{ loading ? "..." : t("login.submit") }}
</button>
</a-button>
</form>
</div>
</div>
@@ -102,14 +122,30 @@ async function handlePasswordLogin() {
@use "@/styles/variables" as *;
.login-view {
position: relative;
height: calc(100 * var(--vh));
display: flex;
align-items: center;
justify-content: center;
background: $bg-primary;
overflow: hidden;
}
// 装饰位面:只负责好看,一律 pointer-events: none(基线硬规则)
.login-decor {
position: absolute;
left: 50%;
bottom: -48px;
transform: translateX(-50%);
width: min(100%, 760px);
opacity: 0.55;
pointer-events: none;
z-index: 0;
}
.login-card {
position: relative;
z-index: 1;
width: 480px;
max-width: calc(100vw - 32px);
padding: 56px;
@@ -156,23 +192,11 @@ async function handlePasswordLogin() {
.login-input {
width: 100%;
padding: 14px 16px;
border: 1px solid $border-color;
border-radius: $radius-sm;
font-size: 15px;
color: $text-primary;
background: $bg-input;
outline: none;
transition: border-color $transition-fast;
box-sizing: border-box;
font-family: $font-code;
&::placeholder {
color: $text-muted;
}
&:focus {
border-color: $accent-primary;
// Arco owns the chrome (border/focus ring); only align type tokens here.
:deep(.arco-input) {
font-family: $font-code;
font-size: 15px;
}
}
@@ -201,25 +225,8 @@ async function handlePasswordLogin() {
}
}
// Arco owns the button chrome (long/size/loading); only align the radius token.
.login-btn {
width: 100%;
padding: 14px;
border: none;
border-radius: $radius-sm;
background: $text-primary;
color: var(--text-on-accent);
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: opacity $transition-fast;
&:hover {
opacity: 0.85;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { NButton, NDrawer, NDrawerContent, NInput, NModal, NPopconfirm, NSpin, NTag, useMessage } from 'naive-ui'
import { NButton, NDrawer, NDrawerContent, NInput, NModal, NPopconfirm, NSpin, NTag } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { copyToClipboard } from '@/utils/clipboard'
import {
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useDialog } from 'naive-ui'
import { useDialog } from '@/composables/useDialog'
import { useI18n } from 'vue-i18n'
import { useFilesStore } from '@/stores/hermes/files'
import { useProfilesStore } from '@/stores/hermes/profiles'
@@ -5,7 +5,8 @@ import { type Session } from '@/stores/hermes/chat'
import { useAppStore } from '@/stores/hermes/app'
import { useProfilesStore } from '@/stores/hermes/profiles'
import { useSessionBrowserPrefsStore } from '@/stores/hermes/session-browser-prefs'
import { NButton, NDropdown, NPopconfirm, NTooltip, useMessage, type DropdownOption } from 'naive-ui'
import { NButton, NDropdown, NPopconfirm, NTooltip, type DropdownOption } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { getSourceLabel } from '@/shared/session-display'
import { copyToClipboard } from '@/utils/clipboard'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { NButton, NDrawer, NDrawerContent, NSpin, NTag, useMessage } from 'naive-ui'
import { NButton, NDrawer, NDrawerContent, NSpin, NTag } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { fetchJourneyGraph, type JourneyGraphResponse, type JourneyMemory, type JourneyNode } from '@/api/hermes/journey'
import { useTheme } from '@/composables/useTheme'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { NButton, NSelect, NSpin, NCollapse, NCollapseItem, NModal, NInput, useMessage } from 'naive-ui'
import { NButton, NSelect, NSpin, NCollapse, NCollapseItem, NModal, NInput } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import KanbanTaskCard from '@/components/hermes/kanban/KanbanTaskCard.vue'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { NSelect, NButton, NSpin, useMessage } from 'naive-ui'
import { NSelect, NButton, NSpin } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { fetchLogFiles, fetchLogs, type LogEntry } from '@/api/hermes/logs'
@@ -1,11 +1,8 @@
<script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue'
import yaml from 'js-yaml'
import {
NAlert, NButton, NEmpty, NInput, NModal,
NSpin, NRadioGroup, NRadioButton, useMessage,
NCheckbox, NScrollbar,
} from 'naive-ui'
import { NAlert, NButton, NEmpty, NInput, NModal, NSpin, NRadioGroup, NRadioButton, NCheckbox, NScrollbar } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import McpServerCard from '@/components/hermes/mcp/McpServerCard.vue'
import {
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { NButton, useMessage } from 'naive-ui'
import { NButton } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue'
import { fetchMemory, saveMemory, type MemoryData } from '@/api/hermes/skills'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { NButton, NSpin, NTabPane, NTabs, useMessage } from 'naive-ui'
import { NButton, NSpin, NTabPane, NTabs } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import AuxiliaryModelsPanel from '@/components/hermes/models/AuxiliaryModelsPanel.vue'
import CombinationModelsPanel from '@/components/hermes/models/CombinationModelsPanel.vue'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { NButton, NSpin, useMessage } from 'naive-ui'
import { NButton, NSpin } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { fetchPerformanceRuntime, type PerformanceRuntimeSnapshot } from '@/api/hermes/performance-monitor'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { NAlert, NButton, NEmpty, NInput, NSelect, NSpin, NTag, useMessage } from 'naive-ui'
import { NAlert, NButton, NEmpty, NInput, NSelect, NSpin, NTag } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { fetchPetdexManifest, type PetdexManifest, type PetdexPet } from '@/api/hermes/petdex'
import { usePetsStore } from '@/stores/hermes/pets'
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { NAlert, NButton, NEmpty, NInput, NSelect, NSpin, NTag, useMessage } from 'naive-ui'
import { NAlert, NButton, NEmpty, NInput, NSelect, NSpin, NTag } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { fetchPlugins, setPluginEnabled, type HermesPluginInfo, type HermesPluginsMetadata } from '@/api/hermes/plugins'
import { useProfilesStore } from '@/stores/hermes/profiles'
@@ -5,7 +5,8 @@ import { FitAddon } from "@xterm/addon-fit";
import { WebLinksAddon } from "@xterm/addon-web-links";
import "@xterm/xterm/css/xterm.css";
import { getApiKey, getBaseUrlValue } from "@/api/client";
import { NButton, NPopconfirm, NTooltip, NSelect, useMessage } from "naive-ui";
import { NButton, NPopconfirm, NTooltip, NSelect } from "naive-ui";
import { useMessage } from "@/composables/useMessage";
import { useI18n } from "vue-i18n";
import type { ITheme } from "@xterm/xterm";
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { NButton, NCheckbox, NDrawer, NDrawerContent, NDropdown, NInput, NModal, NPopconfirm, NSelect, NSpace, NTooltip, useMessage, type DropdownOption } from 'naive-ui'
import { NButton, NCheckbox, NDrawer, NDrawerContent, NDropdown, NInput, NModal, NPopconfirm, NSelect, NSpace, NTooltip, type DropdownOption } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import {
ConnectionLineType,
MarkerType,