diff --git a/apps/memos-local-plugin/adapters/deepseek-harness/index.ts b/apps/memos-local-plugin/adapters/deepseek-harness/index.ts index 53ab3eec4..5c839f104 100644 --- a/apps/memos-local-plugin/adapters/deepseek-harness/index.ts +++ b/apps/memos-local-plugin/adapters/deepseek-harness/index.ts @@ -132,14 +132,15 @@ export function deepSeekHarnessAutoRecoveryEnabled(config: ResolvedConfig): bool config.algorithm.lightweightMemory.enabled; } -/** Locate the prebuilt Viewer assets in source and packed-package layouts. */ -export function resolveDeepSeekHarnessViewerStaticRoot(): string { - const adapterDir = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - resolve(adapterDir, "..", "..", "..", "viewer", "dist"), - resolve(adapterDir, "..", "..", "viewer", "dist"), - ]; - return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0]!; +/** Locate Viewer assets without depending on whether they are built yet. */ +export function resolveDeepSeekHarnessViewerStaticRoot( + adapterDir: string = dirname(fileURLToPath(import.meta.url)), +): string { + const runtimeRoot = resolve(adapterDir, "..", ".."); + const pluginRoot = existsSync(resolve(runtimeRoot, "package.json")) + ? runtimeRoot + : dirname(runtimeRoot); + return resolve(pluginRoot, "viewer", "dist"); } /** Keep the unauthenticated first-run Viewer strictly on local interfaces. */ diff --git a/apps/memos-local-plugin/tests/unit/adapters/deepseek-harness-viewer.test.ts b/apps/memos-local-plugin/tests/unit/adapters/deepseek-harness-viewer.test.ts index 5b90c7f83..3b5e9ed98 100644 --- a/apps/memos-local-plugin/tests/unit/adapters/deepseek-harness-viewer.test.ts +++ b/apps/memos-local-plugin/tests/unit/adapters/deepseek-harness-viewer.test.ts @@ -1,3 +1,5 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; import { resolve } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -233,6 +235,36 @@ describe("DeepSeek Harness Viewer lifecycle", () => { expect(runtime.isDeepSeekHarnessViewerLoopbackHost("192.168.1.20")).toBe(false); }); + it("resolves Viewer assets deterministically in source and packed layouts", async () => { + const runtime = await loadAdapter(); + const fixtureRoot = mkdtempSync(resolve(tmpdir(), "memos-dsh-viewer-layout-")); + // A package root may itself be named `dist`; package.json is the stable + // layout marker. Deliberately leave viewer/dist unbuilt, as in clean CI. + const packageRoot = resolve(fixtureRoot, "dist"); + mkdirSync(resolve(packageRoot, "adapters/deepseek-harness"), { + recursive: true, + }); + mkdirSync(resolve(packageRoot, "dist/adapters/deepseek-harness"), { + recursive: true, + }); + writeFileSync(resolve(packageRoot, "package.json"), "{}\n", "utf8"); + + try { + expect( + runtime.resolveDeepSeekHarnessViewerStaticRoot( + resolve(packageRoot, "adapters/deepseek-harness"), + ), + ).toBe(resolve(packageRoot, "viewer/dist")); + expect( + runtime.resolveDeepSeekHarnessViewerStaticRoot( + resolve(packageRoot, "dist/adapters/deepseek-harness"), + ), + ).toBe(resolve(packageRoot, "viewer/dist")); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } + }); + it("starts the bundled Viewer on the DSH-owned loopback endpoint", async () => { const lifecycle: string[] = []; const runtime = await loadAdapter({ lifecycle });