From 8a8dd7ee48b7bc38f7d29b95c9f014c83bd8aa5d Mon Sep 17 00:00:00 2001 From: chengke <404835780@qq.com> Date: Thu, 8 Oct 2026 17:53:48 +0800 Subject: [PATCH 1/2] refactor(chat): remove evidenceText from ledger and related structures This commit removes the evidenceText property from various types and functions related to evidence handling in the chat domain. The changes include updates to the EvidenceLedgerSnapshot, MutableLedger, and related test files, ensuring that the codebase no longer relies on evidenceText, streamlining evidence management. --- src/agent-harness/answer-context.test.ts | 1 - src/agent-harness/ledger.ts | 12 +- src/agent-harness/types.ts | 1 - src/app/api/memory-areas/[areaId]/route.ts | 17 +++ src/app/api/memory-areas/route.ts | 13 +++ src/app/api/memory-items/[itemId]/route.ts | 17 +++ src/components/chat-composer.tsx | 4 +- src/components/memory-documents.tsx | 94 +++++++++++++++ src/components/sources-panel.tsx | 17 +++ src/components/workspace-shell-layout.tsx | 34 +++++- src/components/workspace-shell.tsx | 5 + src/domains/chat/agent-trace.test.ts | 1 - src/domains/chat/commit-turn.test.ts | 4 - src/domains/chat/index.test.ts | 36 +++--- src/domains/chat/index.ts | 24 ++-- src/domains/chat/service.test.ts | 1 - src/integrations/memento/client.ts | 128 +++++++++++++++++++++ 17 files changed, 360 insertions(+), 49 deletions(-) create mode 100644 src/app/api/memory-areas/[areaId]/route.ts create mode 100644 src/app/api/memory-areas/route.ts create mode 100644 src/app/api/memory-items/[itemId]/route.ts create mode 100644 src/components/memory-documents.tsx diff --git a/src/agent-harness/answer-context.test.ts b/src/agent-harness/answer-context.test.ts index 95ae672..572bde4 100644 --- a/src/agent-harness/answer-context.test.ts +++ b/src/agent-harness/answer-context.test.ts @@ -152,7 +152,6 @@ function makeLedger(): EvidenceLedgerSnapshot { data: Buffer.from(revenuePng).toString("base64"), }, ], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], diff --git a/src/agent-harness/ledger.ts b/src/agent-harness/ledger.ts index 59092ef..937fa70 100644 --- a/src/agent-harness/ledger.ts +++ b/src/agent-harness/ledger.ts @@ -20,7 +20,6 @@ type MutableLedger = { chunks: EvidenceChunk[] assets: EvidenceAsset[] evidence: EvidencePart[] - evidenceText: string[] stopReasons: string[] failureReasons: string[] decisionTraces: unknown[] @@ -57,7 +56,6 @@ export function createEvidenceLedger() { chunks: [], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -70,9 +68,6 @@ export function createEvidenceLedger() { ledger.evidence.push(...readQueryEvidence(response)) - const evidenceText = response.evidenceText?.trim() - if (evidenceText) ledger.evidenceText.push(evidenceText) - const stopReason = response.stopReason?.trim() if (stopReason) ledger.stopReasons.push(stopReason) @@ -193,11 +188,7 @@ export function createEvidenceLedger() { }, hasEvidence(): boolean { - return ( - ledger.chunks.length > 0 || - ledger.evidence.length > 0 || - ledger.evidenceText.length > 0 - ) + return ledger.chunks.length > 0 || ledger.evidence.length > 0 }, snapshot(): EvidenceLedgerSnapshot { @@ -607,7 +598,6 @@ function snapshot(ledger: MutableLedger): EvidenceLedgerSnapshot { chunks: [...ledger.chunks], assets: [...ledger.assets], evidence: [...ledger.evidence], - evidenceText: [...ledger.evidenceText], stopReasons: [...ledger.stopReasons], failureReasons: [...ledger.failureReasons], decisionTraces: [...ledger.decisionTraces], diff --git a/src/agent-harness/types.ts b/src/agent-harness/types.ts index 4566c2e..907e43e 100644 --- a/src/agent-harness/types.ts +++ b/src/agent-harness/types.ts @@ -266,7 +266,6 @@ export type EvidenceLedgerSnapshot = { readonly chunks: readonly EvidenceChunk[] readonly assets: readonly EvidenceAsset[] readonly evidence: readonly EvidencePart[] - readonly evidenceText: readonly string[] readonly stopReasons: readonly string[] readonly failureReasons: readonly string[] readonly decisionTraces: readonly unknown[] diff --git a/src/app/api/memory-areas/[areaId]/route.ts b/src/app/api/memory-areas/[areaId]/route.ts new file mode 100644 index 0000000..ea1ad44 --- /dev/null +++ b/src/app/api/memory-areas/[areaId]/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server" + +import { notebookRequestContext } from "@/domains/workspace/request-context" +import { getMemoryAreaDocument } from "@/integrations/memento/client" +import { withApiErrorResponse } from "@/lib/api-error-response" + +export async function GET( + _request: Request, + context: { params: Promise<{ areaId: string }> }, +): Promise { + return withApiErrorResponse("memory-areas:read", async () => { + const { areaId } = await context.params + const { workspace } = await notebookRequestContext.getAuthenticatedWithClient() + const document = await getMemoryAreaDocument(workspace.id, areaId) + return NextResponse.json(document) + }) +} diff --git a/src/app/api/memory-areas/route.ts b/src/app/api/memory-areas/route.ts new file mode 100644 index 0000000..6071274 --- /dev/null +++ b/src/app/api/memory-areas/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server" + +import { listMemoryAreas } from "@/integrations/memento/client" +import { withApiErrorResponse } from "@/lib/api-error-response" +import { notebookRequestContext } from "@/domains/workspace/request-context" + +export async function GET(): Promise { + return withApiErrorResponse("memory-areas:list", async () => { + const { workspace } = await notebookRequestContext.getAuthenticatedWithClient() + const areas = await listMemoryAreas(workspace.id) + return NextResponse.json({ areas }) + }) +} diff --git a/src/app/api/memory-items/[itemId]/route.ts b/src/app/api/memory-items/[itemId]/route.ts new file mode 100644 index 0000000..54509de --- /dev/null +++ b/src/app/api/memory-items/[itemId]/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server" + +import { notebookRequestContext } from "@/domains/workspace/request-context" +import { deleteMemoryItem } from "@/integrations/memento/client" +import { withApiErrorResponse } from "@/lib/api-error-response" + +export async function DELETE( + _request: Request, + context: { params: Promise<{ itemId: string }> }, +): Promise { + return withApiErrorResponse("memory-items:delete", async () => { + const { itemId } = await context.params + const { workspace } = await notebookRequestContext.getAuthenticatedWithClient() + await deleteMemoryItem(workspace.id, itemId) + return NextResponse.json({ deleted: true }) + }) +} diff --git a/src/components/chat-composer.tsx b/src/components/chat-composer.tsx index 4aab263..f3143cc 100644 --- a/src/components/chat-composer.tsx +++ b/src/components/chat-composer.tsx @@ -214,8 +214,8 @@ export function ChatComposer({ - Deep search plans document selection and navigation for - more thorough answers. Turn off for faster classic search. + Deep search turns on agent retrieval. Turn it off for + classic search without an agent. diff --git a/src/components/memory-documents.tsx b/src/components/memory-documents.tsx new file mode 100644 index 0000000..278702a --- /dev/null +++ b/src/components/memory-documents.tsx @@ -0,0 +1,94 @@ +"use client" + +import { useEffect, useState } from "react" +import type { ReactElement } from "react" + +type MemoryArea = { + readonly id: string + readonly title: string + readonly summaryStatus: "current" | "pending" +} + +type MemoryMember = { + readonly itemId: string + readonly kind: string + readonly status: string + readonly text: string +} + +type MemoryDocument = { + readonly id: string + readonly title: string + readonly summaryStatus: "current" | "pending" + readonly summary: string | null + readonly members: readonly MemoryMember[] +} + +export function MemoryDocuments(props: { + readonly onBack: () => void +}): ReactElement { + const [areas, setAreas] = useState([]) + const [document, setDocument] = useState(null) + + useEffect(() => { + void fetch("/api/memory-areas") + .then((response) => response.json()) + .then((body: { areas?: readonly MemoryArea[] }) => { + setAreas(body.areas ?? []) + }) + }, []) + + async function openArea(areaId: string): Promise { + const response = await fetch(`/api/memory-areas/${areaId}`) + setDocument((await response.json()) as MemoryDocument) + } + + async function deleteItem(itemId: string): Promise { + if (!document) return + await fetch(`/api/memory-items/${itemId}`, { method: "DELETE" }) + await openArea(document.id) + } + + return ( +
+
+ +

+ {document?.title ?? "MEMORY.MD"} +

+
+ {document ? ( +
+ {document.summaryStatus === "current" && document.summary ? ( +

{document.summary}

+ ) : null} +
    + {document.members.map((member) => ( +
  • +

    + {member.kind} {member.status} +

    +

    {member.text}

    + +
  • + ))} +
+
+ ) : ( +
    + {areas.map((area) => ( +
  • + +
  • + ))} +
+ )} +
+ ) +} diff --git a/src/components/sources-panel.tsx b/src/components/sources-panel.tsx index 70f200c..bc02397 100644 --- a/src/components/sources-panel.tsx +++ b/src/components/sources-panel.tsx @@ -55,6 +55,7 @@ export type SourcesPanelProps = { readonly isNarrow?: boolean; readonly addingLibrarySourceIds?: readonly string[]; readonly isLibraryOpen?: boolean; + readonly isMemoryOpen?: boolean; readonly officialLibrarySources?: readonly OfficialLibrarySourceView[]; sources: SourceView[]; onSourceUploaded?: (source: SourceView) => void; @@ -64,6 +65,7 @@ export type SourcesPanelProps = { onArchiveSource?: (sourceId: string) => void; onRetrySource?: (sourceId: string) => void; onLibraryOpen?: () => void; + onMemoryOpen?: () => void; folders?: readonly FolderView[]; currentFolderId?: string | null; creatingFolder?: boolean; @@ -94,6 +96,7 @@ type SourcePageState = { export function SourcesPanel({ isNarrow = false, isLibraryOpen = false, + isMemoryOpen = false, sources = [], onSourceUploaded, selectedSourceId = null, @@ -102,6 +105,7 @@ export function SourcesPanel({ onArchiveSource, onRetrySource, onLibraryOpen, + onMemoryOpen, folders = [], currentFolderId = null, creatingFolder = false, @@ -386,6 +390,19 @@ export function SourcesPanel({ {isNarrow ? null : "open library"} + {onOpenFolder ? ( @@ -141,6 +142,7 @@ export type WorkspaceShellLayoutProps = { readonly onLoginClick: () => void readonly onLibraryBack: () => void readonly onLibraryOpen: () => void + readonly onMemoryOpen: () => void readonly onMobilePanelChange: (panel: PanelId) => void readonly onOfficialLibrarySourceAdd: ( demoSourceId: string, @@ -238,8 +240,10 @@ export function WorkspaceShellLayout( sources={props.sources} selectedSourceId={props.selectedSourceId} isLibraryOpen={props.contentView === "library"} + isMemoryOpen={props.contentView === "memory"} onExpand={() => props.onDesktopPanelExpand("sources")} onLibraryOpen={props.onLibraryOpen} + onMemoryOpen={props.onMemoryOpen} onSourceSelected={props.onSourceSelected} /> ) : ( @@ -247,6 +251,7 @@ export function WorkspaceShellLayout( sources={[...props.sources]} officialLibrarySources={[...officialLibrarySources]} isLibraryOpen={props.contentView === "library"} + isMemoryOpen={props.contentView === "memory"} isNarrow={isSourcesPanelNarrow} analyticsContext={props.analyticsContext} sourceCountSnapshot={props.sources.length} @@ -268,6 +273,7 @@ export function WorkspaceShellLayout( props.isGuest ? undefined : props.onOfficialLibrarySourceAdd } onLibraryOpen={props.onLibraryOpen} + onMemoryOpen={props.onMemoryOpen} archivingSourceIds={[...props.archivingSourceIds]} retryingSourceIds={[...retryingSourceIds]} addingLibrarySourceIds={[...addingLibrarySourceIds]} @@ -309,7 +315,9 @@ export function WorkspaceShellLayout( width: `${props.desktopPanelWidths.chunks}px`, }} > - {props.contentView === "library" ? ( + {props.contentView === "memory" ? ( + + ) : props.contentView === "library" ? ( { @@ -442,6 +451,10 @@ export function WorkspaceShellLayout( props.onLibraryOpen() props.onMobilePanelChange("content") }} + onMemoryOpen={() => { + props.onMemoryOpen() + props.onMobilePanelChange("content") + }} archivingSourceIds={[...props.archivingSourceIds]} retryingSourceIds={[...retryingSourceIds]} addingLibrarySourceIds={[...addingLibrarySourceIds]} @@ -469,7 +482,9 @@ export function WorkspaceShellLayout( props.mobilePanel === "content" ? "flex flex-col" : "hidden" }`} > - {props.contentView === "library" ? ( + {props.contentView === "memory" ? ( + + ) : props.contentView === "library" ? ( void readonly onLibraryOpen?: () => void + readonly onMemoryOpen?: () => void readonly onSourceSelected: (sourceId: string | null) => void readonly selectedSourceId: string | null readonly sources: readonly SourceView[] @@ -655,6 +674,15 @@ function CompactSourcesSidebar({ > + onMemoryOpen?.()} + > + M +
{sources.length === 0 ? ( diff --git a/src/components/workspace-shell.tsx b/src/components/workspace-shell.tsx index 5254403..3c21802 100644 --- a/src/components/workspace-shell.tsx +++ b/src/components/workspace-shell.tsx @@ -178,6 +178,10 @@ function WorkspaceShellContent({ setContentView("library") } + function handleMemoryOpen(): void { + setContentView("memory") + } + function handleLibraryBack(): void { setContentView("chunks") } @@ -279,6 +283,7 @@ function WorkspaceShellContent({ onLoginClick={redirectToLogin} onLibraryBack={handleLibraryBack} onLibraryOpen={handleLibraryOpen} + onMemoryOpen={handleMemoryOpen} onMobilePanelChange={setMobilePanel} onSelectChatThread={chatWorkflow.handleSelectChatThread} onSourceSelected={handleSourceSelected} diff --git a/src/domains/chat/agent-trace.test.ts b/src/domains/chat/agent-trace.test.ts index 24f1b7b..434f4c5 100644 --- a/src/domains/chat/agent-trace.test.ts +++ b/src/domains/chat/agent-trace.test.ts @@ -116,7 +116,6 @@ function makeTrace(overrides: Partial = {}): HarnessTrace { ], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [ diff --git a/src/domains/chat/commit-turn.test.ts b/src/domains/chat/commit-turn.test.ts index e44fa6b..a59d851 100644 --- a/src/domains/chat/commit-turn.test.ts +++ b/src/domains/chat/commit-turn.test.ts @@ -110,7 +110,6 @@ describe("commitChatTurn", () => { ], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -264,7 +263,6 @@ describe("commitChatTurn", () => { ], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -343,7 +341,6 @@ describe("commitChatTurn", () => { chunks: [], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -420,7 +417,6 @@ describe("commitChatTurn", () => { chunks: [], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], diff --git a/src/domains/chat/index.test.ts b/src/domains/chat/index.test.ts index 849753e..c2041b5 100644 --- a/src/domains/chat/index.test.ts +++ b/src/domains/chat/index.test.ts @@ -499,14 +499,14 @@ describe("answerQuestionWithRetrieval", () => { }) .mockResolvedValueOnce({ results: [legacyResult], - evidenceText: "Legacy namespace evidence", - referencedChunks: [], - namespace: "notebook-legacy", - query: "legacy document answer", - routerUsed: "workflow_single_step", - answerText: null, - stopReason: "answer_done", - failureReason: null, + evidenceText: null, + referencedChunks: [], + namespace: "notebook-legacy", + query: "legacy document answer", + routerUsed: "workflow_single_step", + answerText: null, + stopReason: "answer_done", + failureReason: null, }), }; const generateAnswer = vi.fn(async ({ searchSources }) => { @@ -516,7 +516,7 @@ describe("answerQuestionWithRetrieval", () => { stopReason: "answer_done", failureReason: null, results: [legacyResult], - evidenceText: "Legacy namespace evidence", + evidenceText: null, }); return makeHarnessRunResult("The legacy answer is grounded."); }); @@ -871,9 +871,15 @@ describe("answerQuestionWithRetrieval", () => { }), ), ], - evidenceText: `Evidence https://blob.example/evidence.jpg ${"evidence ".repeat( - 80, - )}`, + evidence: [ + { + type: "text" as const, + text: `Evidence https://blob.example/evidence.jpg ${"evidence ".repeat( + 80, + )}`, + }, + ], + evidenceText: null, referencedChunks: [ { chunkId: "chunk_identity_1", @@ -1262,7 +1268,6 @@ describe("answerQuestionWithRetrieval", () => { ledger: { retrievalCount: 1, evidence: [], - evidenceText: ["Identity image evidence."], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -2003,7 +2008,6 @@ describe("answerQuestionWithRetrieval", () => { ledger: { retrievalCount: 1, evidence: [], - evidenceText: ["Identity image candidates."], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -2357,7 +2361,6 @@ describe("answerQuestionWithRetrieval", () => { ledger: { retrievalCount: 1, evidence: [], - evidenceText: ["Diagram candidate."], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -2479,7 +2482,6 @@ describe("answerQuestionWithRetrieval", () => { ledger: { retrievalCount: 1, evidence: [], - evidenceText: ["Plan comparison evidence."], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -3239,7 +3241,6 @@ function makeHarnessRunResult(text: string): HarnessRunResult { chunks: [], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], @@ -3293,7 +3294,6 @@ function makeHarnessRunResultWithLedger( chunks, assets: input.assets ?? [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], diff --git a/src/domains/chat/index.ts b/src/domains/chat/index.ts index 5aba30b..2cf563e 100644 --- a/src/domains/chat/index.ts +++ b/src/domains/chat/index.ts @@ -626,7 +626,7 @@ function formatKnowhereQueryResponseForLog( KNOWHERE_RESPONSE_TEXT_LOG_LIMIT, ), evidenceText: truncateLogText( - response.evidenceText ?? "", + evidencePartsText(response), KNOWHERE_RESPONSE_TEXT_LOG_LIMIT, ), results: response.results @@ -890,10 +890,6 @@ function mergeRetrievalResponses( .slice(0, evidenceLimits.referencedChunkCountPerResponse), ) .slice(0, evidenceLimits.referencedChunkCount) - const evidenceTexts = responses - .map((response) => response.evidenceText) - .filter((value): value is string => Boolean(value)) - .map(truncateAgenticModelText) const answerTexts = responses .map((response) => response.answerText) .filter((value): value is string => Boolean(value)) @@ -904,7 +900,7 @@ function mergeRetrievalResponses( namespace: responses.map((response) => response.namespace).join(","), routerUsed: joinResponseText(responses.map((response) => response.routerUsed)) ?? "", answerText: answerTexts.length > 0 ? answerTexts.join("\n\n") : null, - evidenceText: evidenceTexts.length > 0 ? evidenceTexts.join("\n\n") : null, + evidenceText: null, stopReason: joinResponseText( statusResponses.map((response) => response.stopReason), ), @@ -955,11 +951,25 @@ function hasRetrievalEvidence(response: RetrievalQueryResponse): boolean { return ( response.results.length > 0 || response.referencedChunks.some(hasReferencedChunkEvidence) || - Boolean(response.evidenceText?.trim()) || + hasEvidenceParts(response) || Boolean(response.answerText?.trim()) ) } +function hasEvidenceParts(response: RetrievalQueryResponse): boolean { + return (response.evidence ?? []).some((part) => + part.type === "text" ? Boolean(part.text.trim()) : true, + ) +} + +function evidencePartsText(response: RetrievalQueryResponse): string { + return (response.evidence ?? []) + .flatMap((part) => + part.type === "text" && part.text.trim() ? [part.text] : [], + ) + .join("") +} + function joinResponseText( values: readonly (string | null | undefined)[], ): string | null { diff --git a/src/domains/chat/service.test.ts b/src/domains/chat/service.test.ts index 88ed074..27fce97 100644 --- a/src/domains/chat/service.test.ts +++ b/src/domains/chat/service.test.ts @@ -465,7 +465,6 @@ function makeHarnessRunResult(text: string): HarnessRunResult { chunks: [], assets: [], evidence: [], - evidenceText: [], stopReasons: [], failureReasons: [], decisionTraces: [], diff --git a/src/integrations/memento/client.ts b/src/integrations/memento/client.ts index fb72d6b..4df7d7f 100644 --- a/src/integrations/memento/client.ts +++ b/src/integrations/memento/client.ts @@ -195,6 +195,118 @@ function parseActivations(body: unknown): readonly MementoActivation[] { }) } +export type MementoMemoryArea = { + readonly id: string + readonly title: string + readonly kind: string + readonly summaryStatus: "current" | "pending" +} + +export type MementoMemoryDocument = { + readonly id: string + readonly title: string + readonly summaryStatus: "current" | "pending" + readonly summary: string | null + readonly members: readonly { + readonly itemId: string + readonly kind: string + readonly status: string + readonly text: string + }[] +} + +export async function listMemoryAreas( + workspaceId: string, +): Promise { + const body = await getJson( + `/memory-areas?${new URLSearchParams({ workspaceId }).toString()}`, + ) + return parseMemoryAreas(body) +} + +export async function getMemoryAreaDocument( + workspaceId: string, + areaId: string, +): Promise { + const body = await getJson( + `/memory-areas/${areaId}?${new URLSearchParams({ workspaceId }).toString()}`, + ) + return parseMemoryAreaDocument(body) +} + +export async function deleteMemoryItem( + workspaceId: string, + itemId: string, +): Promise { + await deleteJson( + `/memory-items/${itemId}?${new URLSearchParams({ workspaceId }).toString()}`, + ) +} + +function parseMemoryAreas(body: unknown): readonly MementoMemoryArea[] { + if (typeof body !== "object" || body === null) { + throw new Error("memento memory areas: invalid response") + } + const areas = Reflect.get(body, "areas") + if (!Array.isArray(areas)) { + throw new Error("memento memory areas: invalid response") + } + return areas.flatMap((area) => { + if (typeof area !== "object" || area === null) return [] + const id = Reflect.get(area, "id") + const title = Reflect.get(area, "title") + const kind = Reflect.get(area, "kind") + const summaryStatus = Reflect.get(area, "summaryStatus") + if (typeof id !== "string" || typeof title !== "string" || typeof kind !== "string") { + return [] + } + if (summaryStatus !== "current" && summaryStatus !== "pending") return [] + return [{ id, title, kind, summaryStatus }] + }) +} + +function parseMemoryAreaDocument(body: unknown): MementoMemoryDocument { + if (typeof body !== "object" || body === null) { + throw new Error("memento memory area: invalid response") + } + const id = Reflect.get(body, "id") + const title = Reflect.get(body, "title") + const summaryStatus = Reflect.get(body, "summaryStatus") + const summary = Reflect.get(body, "summary") + const members = Reflect.get(body, "members") + if (typeof id !== "string" || typeof title !== "string") { + throw new Error("memento memory area: invalid response") + } + if (summaryStatus !== "current" && summaryStatus !== "pending") { + throw new Error("memento memory area: invalid response") + } + if (!Array.isArray(members)) { + throw new Error("memento memory area: invalid response") + } + return { + id, + title, + summaryStatus, + summary: typeof summary === "string" ? summary : null, + members: members.flatMap((member) => { + if (typeof member !== "object" || member === null) return [] + const itemId = Reflect.get(member, "itemId") + const kind = Reflect.get(member, "kind") + const status = Reflect.get(member, "status") + const text = Reflect.get(member, "text") + if ( + typeof itemId !== "string" || + typeof kind !== "string" || + typeof status !== "string" || + typeof text !== "string" + ) { + return [] + } + return [{ itemId, kind, status, text }] + }), + } +} + async function getJson(path: string): Promise { const { status, body } = await Effect.runPromise( Effect.gen(function* () { @@ -218,6 +330,22 @@ async function getJson(path: string): Promise { return body } +async function deleteJson(path: string): Promise { + const status = await Effect.runPromise( + Effect.gen(function* () { + const { baseUrl, serviceKey } = readMementoConfig() + const request = HttpClientRequest.del(`${baseUrl}${path}`).pipe( + HttpClientRequest.setHeader("Authorization", `Bearer ${serviceKey}`), + ) + const response = yield* HttpClient.execute(request) + return response.status + }).pipe(Effect.provide(FetchHttpClient.layer)), + ) + if (status < 200 || status >= 300) { + throw new Error(`memento ${path}: HTTP ${status}`) + } +} + async function postJson(path: string, body: unknown): Promise { const status = await Effect.runPromise( Effect.gen(function* () { From 97e2e1b829489567800e59016dd8281b610e8264 Mon Sep 17 00:00:00 2001 From: chengke <404835780@qq.com> Date: Thu, 8 Oct 2026 17:59:18 +0800 Subject: [PATCH 2/2] test(chat): align deep search tooltip assertion The composer copy already changed; keep the test matching the live tooltip. Co-authored-by: Cursor --- src/components/chat-composer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chat-composer.test.ts b/src/components/chat-composer.test.ts index a12264a..f34e985 100644 --- a/src/components/chat-composer.test.ts +++ b/src/components/chat-composer.test.ts @@ -48,7 +48,7 @@ describe("ChatComposer", () => { const tooltip = await screen.findByRole("tooltip"); expect(tooltip.textContent).toContain( - "Deep search plans document selection and navigation", + "Deep search turns on agent retrieval", ); });