






















@@ -90,6 +90,23 @@ type SqliteDatabase = import("node:sqlite").DatabaseSync;
90909191const log = createSubsystemLogger("memory");
929293+/**
94+ * Normalize an already-aborted search signal into the error thrown before any
95+ * qmd work starts. Prefers the caller-supplied abort reason (so a deadline
96+ * message such as "memory_search timed out after 15s" survives) and falls back
97+ * to a stable abort error.
98+ */
99+function asAbortError(signal: AbortSignal): Error {
100+const reason = signal.reason;
101+if (reason instanceof Error) {
102+return reason;
103+}
104+if (typeof reason === "string" && reason.length > 0) {
105+return new Error(reason);
106+}
107+return new Error("qmd search aborted");
108+}
109+93110const SNIPPET_HEADER_RE = /@@\s*-([0-9]+),([0-9]+)/;
94111const SEARCH_PENDING_UPDATE_WAIT_MS = 500;
95112const MAX_QMD_OUTPUT_CHARS = 200_000;
@@ -1280,12 +1297,23 @@ export class QmdMemoryManager implements MemorySearchManager {
12801297qmdSearchModeOverride?: "query" | "search" | "vsearch";
12811298onDebug?: (debug: MemorySearchRuntimeDebug) => void;
12821299sources?: MemorySource[];
1300+/**
1301+ * Caller-owned cancellation. When the caller stops waiting (e.g. the
1302+ * memory_search tool deadline fires), abort kills the in-flight qmd
1303+ * subprocess instead of leaving it running orphaned for the full qmd
1304+ * timeout.
1305+ */
1306+signal?: AbortSignal;
12831307},
12841308): Promise<MemorySearchResult[]> {
12851309if (!this.isScopeAllowed(opts?.sessionKey)) {
12861310this.logScopeDenied(opts?.sessionKey);
12871311return [];
12881312}
1313+const searchSignal = opts?.signal;
1314+if (searchSignal?.aborted) {
1315+throw asAbortError(searchSignal);
1316+}
12891317const trimmed = query.trim();
12901318if (!trimmed) {
12911319return [];
@@ -1374,7 +1402,7 @@ export class QmdMemoryManager implements MemorySearchManager {
13741402}
13751403const args = this.buildSearchArgs(qmdSearchCommand, trimmed, limit);
13761404args.push(...this.buildCollectionFilterArgs(collectionGroups[0] ?? collectionNames));
1377-return await this.runQmdSearch(args, qmdSearchCommand);
1405+return await this.runQmdSearch(args, qmdSearchCommand, searchSignal);
13781406} catch (err) {
13791407if (allowMissingCollectionRepair && this.isMissingCollectionSearchError(err)) {
13801408throw err;
@@ -1403,7 +1431,7 @@ export class QmdMemoryManager implements MemorySearchManager {
14031431fallbackArgs.push(
14041432 ...this.buildCollectionFilterArgs(collectionGroups[0] ?? collectionNames),
14051433);
1406-return await this.runQmdSearch(fallbackArgs, "query");
1434+return await this.runQmdSearch(fallbackArgs, "query", searchSignal);
14071435} catch (fallbackErr) {
14081436log.warn(`qmd query fallback failed: ${String(fallbackErr)}`);
14091437throw fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr));
@@ -2137,7 +2165,7 @@ export class QmdMemoryManager implements MemorySearchManager {
2137216521382166private async runQmd(
21392167args: string[],
2140-opts?: { timeoutMs?: number; discardOutput?: boolean },
2168+opts?: { timeoutMs?: number; discardOutput?: boolean; signal?: AbortSignal },
21412169): Promise<{ stdout: string; stderr: string }> {
21422170return await runCliCommand({
21432171commandSummary: `qmd ${args.join(" ")}`,
@@ -2153,15 +2181,17 @@ export class QmdMemoryManager implements MemorySearchManager {
21532181maxOutputChars: this.maxQmdOutputChars,
21542182// Large `qmd update` runs can easily exceed the output cap; keep only stderr.
21552183discardStdout: opts?.discardOutput,
2184+signal: opts?.signal,
21562185});
21572186}
2158218721592188private async runQmdSearch(
21602189args: string[],
21612190command: "query" | "search" | "vsearch",
2191+signal?: AbortSignal,
21622192): Promise<QmdQueryResult[]> {
21632193try {
2164-const result = await this.runQmd(args, { timeoutMs: this.qmd.limits.timeoutMs });
2194+const result = await this.runQmd(args, { timeoutMs: this.qmd.limits.timeoutMs, signal });
21652195return parseQmdQueryJson(result.stdout, result.stderr);
21662196} catch (err) {
21672197const recovered = this.parseFailedQmdSearchJson(err, command);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。