diff --git a/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts b/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts
new file mode 100644
index 00000000..5fc9a01d
--- /dev/null
+++ b/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it, vi } from 'vitest'
+import { openWikiFailureItem } from '../utils/failureOpen'
+
+describe('openWikiFailureItem', () => {
+ it('switches to the owning workspace before opening a cross-workspace KB', async () => {
+ const calls: string[] = []
+ const workspaceStore = {
+ currentWorkspaceId: 'ws-a',
+ switchWorkspace: vi.fn(async (id: string) => {
+ calls.push(`switch:${id}`)
+ }),
+ }
+ const wikiStore = {
+ selectKB: vi.fn(async (id: string, mode: 'browse' | 'manage') => {
+ calls.push(`select:${id}:${mode}`)
+ }),
+ }
+
+ await openWikiFailureItem(
+ { kbId: 'kb-2', workspaceId: 'ws-b' },
+ { workspaceStore, wikiStore },
+ )
+
+ expect(calls).toEqual(['switch:ws-b', 'select:kb-2:browse'])
+ })
+
+ it('opens directly when the failure item belongs to the active workspace', async () => {
+ const workspaceStore = {
+ currentWorkspaceId: 'ws-a',
+ switchWorkspace: vi.fn(),
+ }
+ const wikiStore = {
+ selectKB: vi.fn(async () => {}),
+ }
+
+ await openWikiFailureItem(
+ { kbId: 'kb-1', workspaceId: 'ws-a' },
+ { workspaceStore, wikiStore },
+ )
+
+ expect(workspaceStore.switchWorkspace).not.toHaveBeenCalled()
+ expect(wikiStore.selectKB).toHaveBeenCalledWith('kb-1', 'browse')
+ })
+})
diff --git a/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue b/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue
index a5dded70..35ac860a 100644
--- a/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue
+++ b/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue
@@ -16,7 +16,7 @@
-
+
{{ t(`wiki.status.${it.processingStatus}`) }}
@@ -25,7 +25,7 @@
{{ friendly(it) }}
-
+
@@ -36,7 +36,7 @@ import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { wikiApi, type WikiFailureItem } from '@/api/index'
-defineEmits<{ (e: 'open', kbId: string): void }>()
+defineEmits<{ (e: 'open', item: WikiFailureItem): void }>()
const { t } = useI18n()
const items = ref
([])
diff --git a/mateclaw-ui/src/views/Wiki/index.vue b/mateclaw-ui/src/views/Wiki/index.vue
index 190258e0..4ee755e7 100644
--- a/mateclaw-ui/src/views/Wiki/index.vue
+++ b/mateclaw-ui/src/views/Wiki/index.vue
@@ -48,18 +48,21 @@ import { ref, reactive, watch, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { useWikiStore, type WikiKB } from '@/stores/useWikiStore'
-import { wikiApi } from '@/api/index'
+import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
+import { wikiApi, type WikiFailureItem } from '@/api/index'
import { mcConfirm } from '@/components/common/useConfirm'
import { mcToast } from '@/composables/useMcToast'
import WikiLibrary from './components/WikiLibrary.vue'
import WikiWorkspace from './components/WikiWorkspace.vue'
import WikiFailureCenter from './components/WikiFailureCenter.vue'
+import { openWikiFailureItem } from './utils/failureOpen'
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const store = useWikiStore()
+const workspaceStore = useWorkspaceStore()
// The cross-KB failure center spans every workspace, so it is admin-only —
// mirrors the gate on the backing endpoint.
@@ -95,10 +98,8 @@ async function enterKB(id: number) {
await store.selectKB(id, 'browse')
}
-// The failure center emits a Snowflake kbId as a string — keep it a string end
-// to end (snowflake-precision-ok) and let the store cast satisfy its signature.
-async function openFromFailureCenter(kbId: string) {
- await store.selectKB(kbId as unknown as number, 'browse')
+async function openFromFailureCenter(item: WikiFailureItem) {
+ await openWikiFailureItem(item, { workspaceStore, wikiStore: store })
}
async function enterKBManage(id: number) {
diff --git a/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts b/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts
new file mode 100644
index 00000000..dee0a9f8
--- /dev/null
+++ b/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts
@@ -0,0 +1,27 @@
+export interface WikiFailureOpenItem {
+ kbId: string
+ workspaceId?: string | null
+}
+
+export interface WikiFailureOpenDeps {
+ workspaceStore: {
+ currentWorkspaceId: string | null
+ switchWorkspace: (id: string) => Promise | void
+ }
+ wikiStore: {
+ // The wiki store is still typed as number in places, but Snowflake KB IDs
+ // are passed as strings at runtime to avoid precision loss.
+ selectKB: (id: any, mode: 'browse' | 'manage') => Promise | void
+ }
+}
+
+export async function openWikiFailureItem(
+ item: WikiFailureOpenItem,
+ deps: WikiFailureOpenDeps,
+) {
+ const targetWorkspaceId = item.workspaceId || null
+ if (targetWorkspaceId && deps.workspaceStore.currentWorkspaceId !== targetWorkspaceId) {
+ await deps.workspaceStore.switchWorkspace(targetWorkspaceId)
+ }
+ await deps.wikiStore.selectKB(item.kbId, 'browse')
+}