om-memory-system 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/adapters/claude-code/hook-client.d.ts +1 -1
- package/dist/adapters/claude-code/hook-client.js +15 -2
- package/dist/adapters/opencode/in-process-client.d.ts +2 -0
- package/dist/adapters/opencode/in-process-client.js +16 -0
- package/dist/adapters/opencode/opencode-host-config.d.ts +3 -0
- package/dist/adapters/opencode/opencode-host-config.js +2 -0
- package/dist/adapters/opencode/tui-recall.d.ts +68 -0
- package/dist/adapters/opencode/tui-recall.js +312 -0
- package/dist/adapters/pi/extension.d.ts +1 -0
- package/dist/adapters/pi/extension.js +28 -11
- package/dist/adapters/pi/recall-rows.d.ts +31 -0
- package/dist/adapters/pi/recall-rows.js +148 -0
- package/dist/adapters/pi/session-context.d.ts +22 -0
- package/dist/adapters/pi/session-context.js +125 -0
- package/dist/config-template.d.ts +1 -1
- package/dist/config-template.js +20 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +8 -4
- package/dist/core/context-budget.d.ts +2 -0
- package/dist/core/context-budget.js +2 -1
- package/dist/core/opening-context.d.ts +45 -0
- package/dist/core/opening-context.js +60 -0
- package/dist/core/recall-summary.d.ts +32 -0
- package/dist/core/recall-summary.js +77 -0
- package/dist/core/retrieval.d.ts +16 -0
- package/dist/core/retrieval.js +33 -11
- package/dist/importer/claude-hook-api.d.ts +5 -4
- package/dist/importer/claude-hook-api.js +58 -41
- package/dist/importer/claude-hook-context.d.ts +49 -0
- package/dist/importer/claude-hook-context.js +264 -0
- package/dist/index.js +1 -1
- package/dist/services/ai/local-api-url.d.ts +2 -0
- package/dist/services/ai/local-api-url.js +12 -0
- package/dist/services/ai/provider-config.d.ts +2 -0
- package/dist/services/ai/provider-config.js +3 -1
- package/dist/services/ai/providers/google-gemini.js +2 -1
- package/dist/services/ai/providers/openai-responses.js +2 -1
- package/dist/services/chat/chat-runtime.js +5 -1
- package/dist/services/chat/chat-status.js +4 -1
- package/dist/services/global-config-writer.js +39 -14
- package/dist/services/injection-options-config.d.ts +53 -0
- package/dist/services/injection-options-config.js +56 -0
- package/dist/services/settings-snapshot.js +44 -12
- package/dist/services/single-flight.d.ts +2 -0
- package/dist/services/single-flight.js +12 -0
- package/dist/services/visible-recall-config.d.ts +39 -0
- package/dist/services/visible-recall-config.js +28 -0
- package/dist/services/web-server.d.ts +3 -0
- package/dist/services/web-server.js +26 -8
- package/dist/utils/memory-limits.d.ts +2 -1
- package/dist/utils/memory-limits.js +6 -3
- package/dist/v2/adapter.d.ts +2 -0
- package/dist/v2/adapter.js +110 -31
- package/dist/v2/legacy-client.js +2 -1
- package/dist/v2/memory-bridge.d.ts +15 -0
- package/dist/v2/memory-bridge.js +21 -0
- package/dist/v2/recall-reporter.d.ts +27 -0
- package/dist/v2/recall-reporter.js +105 -0
- package/dist/v2/recall-rpc.d.ts +51 -0
- package/dist/v2/recall-rpc.js +38 -0
- package/dist/v2/session-parent.d.ts +14 -0
- package/dist/v2/session-parent.js +26 -0
- package/dist/web/assets/{index-CJ_K-hMG.js → index-BhQR88tU.js} +16 -16
- package/dist/web/assets/index-BmFUZbGr.css +2 -0
- package/dist/web/index.html +2 -2
- package/opencode/tui.tsx +22 -3
- package/package.json +1 -1
- package/dist/web/assets/index-ccH97VE2.css +0 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** One memory a recall injected, reduced to what a fold row shows. A restored memory has no relevance. */
|
|
2
|
+
export type RecallItem = {
|
|
3
|
+
relevance: number | null;
|
|
4
|
+
id?: string;
|
|
5
|
+
headline: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* One recall as OMMS made it: a prompt search, the opening search with the configured query, a
|
|
9
|
+
* restored session after compaction or resume, or the recent memories a session opens with.
|
|
10
|
+
*/
|
|
11
|
+
export type RecallSummary = {
|
|
12
|
+
mode: "search" | "recent" | "opening" | "restore";
|
|
13
|
+
query: string | null;
|
|
14
|
+
items: RecallItem[];
|
|
15
|
+
/** Per-session counter the v2 reporter sets when it stores the summary; the TUI keys rows by it. */
|
|
16
|
+
seq?: number;
|
|
17
|
+
};
|
|
18
|
+
/** Query on one line, cut to `max` characters. */
|
|
19
|
+
export declare function queryLine(query: string, max?: number): string;
|
|
20
|
+
/** One line that names a memory: the first line under `## Request`, else the first line. */
|
|
21
|
+
export declare function headline(body: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Summarise a formatted retrieval section for a host to show. Returns null when the section holds
|
|
24
|
+
* no memory (a profile-only section included), so the host draws nothing. A `restore` summary
|
|
25
|
+
* reads the restored-session entries, with no relevance. A `restore` or `recent` recall has no
|
|
26
|
+
* query, whatever the caller passes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildRecallSummary(section: string, mode: RecallSummary["mode"], query: string | null): RecallSummary | null;
|
|
29
|
+
/** `memory [mode=search, query="...", limit=4]`: the call's name and arguments, as a tool row shows them. */
|
|
30
|
+
export declare function callTitle(summary: RecallSummary): string;
|
|
31
|
+
/** Body of an unfolded row, one string per line, without indentation (the host indents). */
|
|
32
|
+
export declare function unfoldLines(summary: RecallSummary): string[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const QUERY_CUT = 60;
|
|
2
|
+
const UNFOLDED_QUERY_CUT = 300;
|
|
3
|
+
const HEADLINE_CUT = 140;
|
|
4
|
+
/** Query on one line, cut to `max` characters. */
|
|
5
|
+
export function queryLine(query, max = QUERY_CUT) {
|
|
6
|
+
const line = query.replace(/\s+/g, " ").trim();
|
|
7
|
+
return line.length > max ? `${line.slice(0, max - 3)}...` : line;
|
|
8
|
+
}
|
|
9
|
+
// Entry shape comes from `formatContextForPrompt`: `<memory id="..." relevance="N%">`, with the
|
|
10
|
+
// id missing when the result had none. A body ends at its own `</memory>` when another entry, a
|
|
11
|
+
// section close or the end of the text follows it. The Claude Code hook can cut a block short and
|
|
12
|
+
// leave tags open, so a body also ends at a section close or at the end of the text. An opening
|
|
13
|
+
// tag inside the text, even at the start of a line, does not end it: a memory can quote the tag.
|
|
14
|
+
const SECTION_CLOSE = String.raw `<\/project_knowledge>|<\/memory_context>|<\/omms-retrieval>`;
|
|
15
|
+
const MEMORY_ENTRY = new RegExp(String.raw `<memory(?:\s+id="([^"]*)")?\s+relevance="(\d+)%">([\s\S]*?)` +
|
|
16
|
+
String.raw `(?=<\/memory>\s*(?:<memory\s|${SECTION_CLOSE}|$)|${SECTION_CLOSE}|$)`, "g");
|
|
17
|
+
// A restored-session section from `formatMemoriesForCompaction`: `### Memory N (id)`, no relevance.
|
|
18
|
+
// Same rule as the Claude Code parser (`hooks/omms-recall-parse.js`).
|
|
19
|
+
const RESTORED_ENTRY = /### Memory \d+(?: \(([^)\n]+)\))?\n([\s\S]*?)(?=### Memory \d+|<\/omms-retrieval>|$)/g;
|
|
20
|
+
/** One line that names a memory: the first line under `## Request`, else the first line. */
|
|
21
|
+
export function headline(body) {
|
|
22
|
+
const lines = body
|
|
23
|
+
.replace(/<\/memory>\s*$/, "")
|
|
24
|
+
.split("\n")
|
|
25
|
+
.map((line) => line.trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
const heading = lines.findIndex((line) => /^##\s*request\s*$/i.test(line));
|
|
28
|
+
const line = (heading >= 0 ? lines[heading + 1] : lines[0]) ?? "";
|
|
29
|
+
return line.length > HEADLINE_CUT ? `${line.slice(0, HEADLINE_CUT - 3)}...` : line;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Summarise a formatted retrieval section for a host to show. Returns null when the section holds
|
|
33
|
+
* no memory (a profile-only section included), so the host draws nothing. A `restore` summary
|
|
34
|
+
* reads the restored-session entries, with no relevance. A `restore` or `recent` recall has no
|
|
35
|
+
* query, whatever the caller passes.
|
|
36
|
+
*/
|
|
37
|
+
export function buildRecallSummary(section, mode, query) {
|
|
38
|
+
const items = [];
|
|
39
|
+
const restore = mode === "restore";
|
|
40
|
+
for (const match of section.matchAll(restore ? RESTORED_ENTRY : MEMORY_ENTRY)) {
|
|
41
|
+
const text = headline((restore ? match[2] : match[3]) ?? "");
|
|
42
|
+
if (!text)
|
|
43
|
+
continue;
|
|
44
|
+
const item = { relevance: restore ? null : Number(match[2]), headline: text };
|
|
45
|
+
if (match[1])
|
|
46
|
+
item.id = match[1];
|
|
47
|
+
items.push(item);
|
|
48
|
+
}
|
|
49
|
+
if (items.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
return { mode, query: restore || mode === "recent" ? null : query, items };
|
|
52
|
+
}
|
|
53
|
+
/** Escape backslashes and double quotes so the quoted value in the title reads as one string. */
|
|
54
|
+
function escapeQuoted(text) {
|
|
55
|
+
return text.replace(/[\\"]/g, "\\$&");
|
|
56
|
+
}
|
|
57
|
+
/** `memory [mode=search, query="...", limit=4]`: the call's name and arguments, as a tool row shows them. */
|
|
58
|
+
export function callTitle(summary) {
|
|
59
|
+
const args = [`mode=${summary.mode}`];
|
|
60
|
+
if (summary.query)
|
|
61
|
+
args.push(`query="${escapeQuoted(queryLine(summary.query))}"`);
|
|
62
|
+
args.push(`limit=${summary.items.length}`);
|
|
63
|
+
return `memory [${args.join(", ")}]`;
|
|
64
|
+
}
|
|
65
|
+
/** Body of an unfolded row, one string per line, without indentation (the host indents). */
|
|
66
|
+
export function unfoldLines(summary) {
|
|
67
|
+
const lines = [`mode: ${summary.mode}`];
|
|
68
|
+
if (summary.query)
|
|
69
|
+
lines.push(`query: ${queryLine(summary.query, UNFOLDED_QUERY_CUT)}`);
|
|
70
|
+
lines.push(`limit: ${summary.items.length}`);
|
|
71
|
+
summary.items.forEach((item, index) => {
|
|
72
|
+
const relevance = item.relevance === null ? "" : `${item.relevance}%`;
|
|
73
|
+
const id = item.id ? `id=${item.id}` : "";
|
|
74
|
+
lines.push(`${index + 1}. ${[relevance, id, item.headline].filter(Boolean).join(" ")}`);
|
|
75
|
+
});
|
|
76
|
+
return lines;
|
|
77
|
+
}
|
package/dist/core/retrieval.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { type ContextBudgetOptions } from "./context-budget.js";
|
|
2
2
|
export declare const RETRIEVAL_SECTION_TAG = "omms-retrieval";
|
|
3
|
+
/**
|
|
4
|
+
* The search step of every query-driven section (`buildRetrievalSection` and
|
|
5
|
+
* the opening search in `opening-context.ts`), so they share one rule set: the
|
|
6
|
+
* store applies the result limit and the relevance threshold, then memories
|
|
7
|
+
* from the current session are dropped when `chatMessage.excludeCurrentSession`
|
|
8
|
+
* is on. Returns null when the search failed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function searchSectionMemories(query: string, projectTag: string, sessionId: string): Promise<{
|
|
11
|
+
results: any[];
|
|
12
|
+
queryVector?: ArrayLike<number>;
|
|
13
|
+
} | null>;
|
|
14
|
+
/**
|
|
15
|
+
* The prompt text that may reach a search: `<private>` regions removed. Null when nothing is
|
|
16
|
+
* left, so a fully private prompt runs no search and nothing of it reaches an embedding provider.
|
|
17
|
+
*/
|
|
18
|
+
export declare function searchablePrompt(prompt: string): string | null;
|
|
3
19
|
/**
|
|
4
20
|
* Prompt-aware semantic retrieval shared by every host (Pi `before_agent_start`,
|
|
5
21
|
* OpenCode v2 `prompt`/`context` hooks): search the shared project memory with
|
package/dist/core/retrieval.js
CHANGED
|
@@ -2,31 +2,53 @@ import { CONFIG } from "../config.js";
|
|
|
2
2
|
import { formatContextForPrompt } from "../services/context.js";
|
|
3
3
|
import { memoryClient } from "../services/client.js";
|
|
4
4
|
import { getTags } from "../services/tags.js";
|
|
5
|
+
import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
|
|
5
6
|
import { utf8ByteLength } from "../utils/context-limit.js";
|
|
6
7
|
import { packContextSection } from "./context-budget.js";
|
|
7
8
|
export const RETRIEVAL_SECTION_TAG = "omms-retrieval";
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
10
|
+
* The search step of every query-driven section (`buildRetrievalSection` and
|
|
11
|
+
* the opening search in `opening-context.ts`), so they share one rule set: the
|
|
12
|
+
* store applies the result limit and the relevance threshold, then memories
|
|
13
|
+
* from the current session are dropped when `chatMessage.excludeCurrentSession`
|
|
14
|
+
* is on. Returns null when the search failed.
|
|
13
15
|
*/
|
|
14
|
-
export async function
|
|
15
|
-
if (!prompt.trim())
|
|
16
|
-
return null;
|
|
16
|
+
export async function searchSectionMemories(query, projectTag, sessionId) {
|
|
17
17
|
// Snapshot before the async search: an excludeCurrentSession edit during
|
|
18
18
|
// the search must not change this operation's filtering.
|
|
19
19
|
const excludeCurrentSession = CONFIG.chatMessage.excludeCurrentSession;
|
|
20
|
-
const
|
|
21
|
-
const search = await memoryClient.searchMemories(prompt, tags.project.tag);
|
|
20
|
+
const search = await memoryClient.searchMemories(query, projectTag);
|
|
22
21
|
if (!search.success)
|
|
23
22
|
return null;
|
|
24
23
|
const results = excludeCurrentSession
|
|
25
24
|
? search.results.filter((result) => result.metadata?.sessionID !== sessionId)
|
|
26
25
|
: search.results;
|
|
27
|
-
|
|
26
|
+
return { results, queryVector: search.queryVector };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The prompt text that may reach a search: `<private>` regions removed. Null when nothing is
|
|
30
|
+
* left, so a fully private prompt runs no search and nothing of it reaches an embedding provider.
|
|
31
|
+
*/
|
|
32
|
+
export function searchablePrompt(prompt) {
|
|
33
|
+
if (isFullyPrivate(prompt))
|
|
34
|
+
return null;
|
|
35
|
+
return stripPrivateContent(prompt).trim() || null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Prompt-aware semantic retrieval shared by every host (Pi `before_agent_start`,
|
|
39
|
+
* OpenCode v2 `prompt`/`context` hooks): search the shared project memory with
|
|
40
|
+
* the incoming prompt and format a bounded context section (memory + user
|
|
41
|
+
* profile). Returns null when nothing relevant is found.
|
|
42
|
+
*/
|
|
43
|
+
export async function buildRetrievalSection(prompt, directory, sessionId, budget) {
|
|
44
|
+
const query = searchablePrompt(prompt);
|
|
45
|
+
if (!query)
|
|
46
|
+
return null;
|
|
47
|
+
const tags = getTags(directory);
|
|
48
|
+
const search = await searchSectionMemories(query, tags.project.tag, sessionId);
|
|
49
|
+
if (!search || search.results.length === 0)
|
|
28
50
|
return null;
|
|
29
|
-
const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, { results }, budget, search.queryVector);
|
|
51
|
+
const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, { results: search.results }, budget, search.queryVector);
|
|
30
52
|
return memoryContext || null;
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
@@ -15,10 +15,11 @@ export declare class ClaudeHookRequestError extends Error {
|
|
|
15
15
|
constructor(message: string);
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* `POST /api/claude/retrieve`:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* `POST /api/claude/retrieve`: the retrieval section for a prompt (and the
|
|
19
|
+
* opening section on a session's first prompt, when on), the memories for a
|
|
20
|
+
* starting subagent's task, or the session's own memories after compaction or
|
|
21
|
+
* resume. A fresh or cleared session adds nothing. Every text is wrapped in the
|
|
22
|
+
* retrieval tag; an empty string means nothing to add.
|
|
22
23
|
*/
|
|
23
24
|
export declare function handleClaudeRetrieve(body: unknown, deps?: ClaudeHookDeps): Promise<{
|
|
24
25
|
additionalContext: string;
|
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
4
|
import { CONFIG, refreshConfigIfChanged } from "../config.js";
|
|
5
5
|
import { classifyCaptureFailure, quickRetryDelayMs } from "../core/capture-retry-policy.js";
|
|
6
|
-
import {
|
|
6
|
+
import { formatMemoriesForCompaction } from "../core/retrieval.js";
|
|
7
7
|
import { handleFinishedTurn } from "../core/turn-outputs.js";
|
|
8
8
|
import { DEFAULT_RETRIEVAL_MAX_TOKENS, tokensToByteCeiling } from "../core/context-budget.js";
|
|
9
9
|
import { resolveClaudeCodeLiveModel } from "../services/ai/live-model-choice.js";
|
|
@@ -11,15 +11,15 @@ import { captureFailureOf, drainCaptureRetries, queueFailedCapture, registerCapt
|
|
|
11
11
|
import { readClaudeCaptureCursor, writeClaudeCaptureCursor, } from "../services/claude-capture-cursor.js";
|
|
12
12
|
import { memoryClient } from "../services/client.js";
|
|
13
13
|
import { log } from "../services/logger.js";
|
|
14
|
-
import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
|
|
15
14
|
import { skillTurnRecorder } from "../services/skill-learning/settings-for.js";
|
|
16
15
|
import { getTags } from "../services/tags.js";
|
|
17
16
|
import { parseClaudeTranscript } from "./claude-conversation.js";
|
|
17
|
+
import { markSessionSeen, promptContext, sectionBudget, sectionLimits, subagentContext, wrapSection, } from "./claude-hook-context.js";
|
|
18
18
|
import { learnClaudeProfile } from "./claude-profile-learning.js";
|
|
19
19
|
import { claudeWindowsToCapture, recordClaudeSkillTurns, sessionDirectory, skillUnitOf, workUnitOf, } from "./claude-turn-units.js";
|
|
20
20
|
import { defaultClaudeProjectsRoot } from "./claude-reader.js";
|
|
21
21
|
// The web app's side of the Claude Code hooks (design decisions 4, 6, 7):
|
|
22
|
-
// retrieval for SessionStart and
|
|
22
|
+
// retrieval for SessionStart, UserPromptSubmit and SubagentStart, a one-worker capture queue
|
|
23
23
|
// for Stop, profile learning, and the once-per-process backfill start. Logs
|
|
24
24
|
// carry event names, session ids, counts, sizes, and codes; never text.
|
|
25
25
|
const HOST = "claude-code";
|
|
@@ -31,6 +31,7 @@ export class ClaudeHookRequestError extends Error {
|
|
|
31
31
|
this.name = "ClaudeHookRequestError";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
const RETRIEVE_EVENTS = ["session-start", "user-prompt-submit", "subagent-start"];
|
|
34
35
|
function objectBody(body) {
|
|
35
36
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
36
37
|
throw new ClaudeHookRequestError("A JSON object is required");
|
|
@@ -57,14 +58,17 @@ function optionalString(body, key) {
|
|
|
57
58
|
}
|
|
58
59
|
function parseRetrieveRequest(body) {
|
|
59
60
|
const input = objectBody(body);
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const event = RETRIEVE_EVENTS.find((name) => name === input.event);
|
|
62
|
+
if (!event) {
|
|
63
|
+
throw new ClaudeHookRequestError(`event must be one of ${RETRIEVE_EVENTS.join(", ")}`);
|
|
62
64
|
}
|
|
63
65
|
return {
|
|
64
|
-
event
|
|
66
|
+
event,
|
|
65
67
|
...sessionAndCwd(input),
|
|
66
68
|
source: optionalString(input, "source"),
|
|
67
69
|
prompt: optionalString(input, "prompt"),
|
|
70
|
+
transcriptPath: optionalString(input, "transcript_path"),
|
|
71
|
+
agentType: optionalString(input, "agent_type"),
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
@@ -140,44 +144,58 @@ function maybeStartBackfill(deps) {
|
|
|
140
144
|
.startBackfill()
|
|
141
145
|
.catch((error) => log("Claude Code backfill start failed", { code: errorCode(error) }, "error"));
|
|
142
146
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
/** The checked transcript path of the request, or undefined when it has none or fails the check. */
|
|
148
|
+
function requestTranscript(request) {
|
|
149
|
+
if (!request.transcriptPath)
|
|
150
|
+
return undefined;
|
|
151
|
+
try {
|
|
152
|
+
return checkedTranscriptPath(request.transcriptPath, request.sessionId);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** The text to add for one request, already wrapped in the retrieval tag. Empty means nothing. */
|
|
159
|
+
async function retrievalText(request, budgetBytes) {
|
|
160
|
+
const limits = sectionLimits(budgetBytes);
|
|
147
161
|
if (request.event === "user-prompt-submit") {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
return promptContext({
|
|
163
|
+
prompt: request.prompt ?? "",
|
|
164
|
+
cwd: request.cwd,
|
|
165
|
+
sessionId: request.sessionId,
|
|
166
|
+
transcriptPath: requestTranscript(request),
|
|
167
|
+
}, limits);
|
|
153
168
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
: [];
|
|
162
|
-
return own.length > 0 ? formatMemoriesForCompaction(own, budget) : null;
|
|
169
|
+
if (request.event === "subagent-start") {
|
|
170
|
+
return subagentContext({
|
|
171
|
+
cwd: request.cwd,
|
|
172
|
+
sessionId: request.sessionId,
|
|
173
|
+
transcriptPath: requestTranscript(request),
|
|
174
|
+
agentType: request.agentType,
|
|
175
|
+
}, budgetBytes);
|
|
163
176
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
// A fresh or cleared session adds nothing; the opening section waits for the first prompt.
|
|
178
|
+
if (request.source !== "compact" && request.source !== "resume")
|
|
179
|
+
return "";
|
|
180
|
+
// A compacted or resumed session already has history, so it never gets an opening section.
|
|
181
|
+
markSessionSeen(request.sessionId);
|
|
182
|
+
if (!CONFIG.compaction.enabled)
|
|
183
|
+
return "";
|
|
184
|
+
const tags = getTags(request.cwd);
|
|
185
|
+
const result = await memoryClient.searchMemoriesBySessionID(request.sessionId, tags.project.tag, CONFIG.compaction.memoryLimit);
|
|
186
|
+
const own = result.success
|
|
187
|
+
? result.results.filter((memory) => memory.metadata?.host === HOST)
|
|
188
|
+
: [];
|
|
189
|
+
return own.length > 0
|
|
190
|
+
? wrapSection(formatMemoriesForCompaction(own, sectionBudget(limits)), limits)
|
|
191
|
+
: "";
|
|
175
192
|
}
|
|
176
193
|
/**
|
|
177
|
-
* `POST /api/claude/retrieve`:
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
194
|
+
* `POST /api/claude/retrieve`: the retrieval section for a prompt (and the
|
|
195
|
+
* opening section on a session's first prompt, when on), the memories for a
|
|
196
|
+
* starting subagent's task, or the session's own memories after compaction or
|
|
197
|
+
* resume. A fresh or cleared session adds nothing. Every text is wrapped in the
|
|
198
|
+
* retrieval tag; an empty string means nothing to add.
|
|
181
199
|
*/
|
|
182
200
|
export async function handleClaudeRetrieve(body, deps = {}) {
|
|
183
201
|
const request = parseRetrieveRequest(body);
|
|
@@ -189,8 +207,7 @@ export async function handleClaudeRetrieve(body, deps = {}) {
|
|
|
189
207
|
if (request.event === "session-start")
|
|
190
208
|
maybeStartBackfill(deps);
|
|
191
209
|
try {
|
|
192
|
-
|
|
193
|
-
return { additionalContext: section ? wrapRetrievalSection(section) : "" };
|
|
210
|
+
return { additionalContext: await retrievalText(request, budgetBytes) };
|
|
194
211
|
}
|
|
195
212
|
catch (error) {
|
|
196
213
|
log("Claude Code retrieval failed", {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ContextBudgetOptions } from "../core/context-budget.js";
|
|
2
|
+
/** Tells the recall rows to draw nothing for this section. 21 bytes, newline included. */
|
|
3
|
+
export declare const ROWS_OFF_MARKER = "<!--omms:rows-off-->\n";
|
|
4
|
+
/** The byte budget of one request and whether recall rows are off for it. */
|
|
5
|
+
export interface SectionLimits {
|
|
6
|
+
budgetBytes: number;
|
|
7
|
+
rowsOff: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Read `visibleRecall` once per request, after the config refresh. */
|
|
10
|
+
export declare function sectionLimits(budgetBytes: number): SectionLimits;
|
|
11
|
+
/** The budget for one packed section: the retrieval tag, and the rows-off marker when set, are reserved. */
|
|
12
|
+
export declare function sectionBudget(limits: SectionLimits): ContextBudgetOptions;
|
|
13
|
+
/** Put a marker line right after the opening tag of a wrapped section. */
|
|
14
|
+
export declare function insertMarker(wrapped: string, marker: string): string;
|
|
15
|
+
/** Wrap a packed section, with the rows-off marker when rows are off. */
|
|
16
|
+
export declare function wrapSection(section: string, limits: SectionLimits): string;
|
|
17
|
+
/**
|
|
18
|
+
* The opening marker. The query goes on one line, with `"`, `<` and `>`
|
|
19
|
+
* removed, `--` folded to `-` and 300 characters at most, so it cannot end the
|
|
20
|
+
* comment early.
|
|
21
|
+
*/
|
|
22
|
+
export declare function openingMarker(query: string): string;
|
|
23
|
+
/** Remember a session, so a later prompt in this process is never first. Oldest ids drop out. */
|
|
24
|
+
export declare function markSessionSeen(sessionId: string): void;
|
|
25
|
+
export interface PromptContextInput {
|
|
26
|
+
prompt: string;
|
|
27
|
+
cwd: string;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
/** The checked transcript path, or undefined when the request had none or it failed the check. */
|
|
30
|
+
transcriptPath: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The added context for a prompt: its retrieval section, and on the first
|
|
34
|
+
* prompt of a session, with `openingContext` on, the opening section after it.
|
|
35
|
+
* Empty string when there is nothing to add.
|
|
36
|
+
*/
|
|
37
|
+
export declare function promptContext(input: PromptContextInput, limits: SectionLimits): Promise<string>;
|
|
38
|
+
export interface SubagentContextInput {
|
|
39
|
+
cwd: string;
|
|
40
|
+
sessionId: string;
|
|
41
|
+
transcriptPath: string | undefined;
|
|
42
|
+
agentType: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The added context for a starting subagent: the retrieval section for its
|
|
46
|
+
* task prompt, read from the parent transcript. Every subagent section carries
|
|
47
|
+
* the rows-off marker. Any problem gives an empty string.
|
|
48
|
+
*/
|
|
49
|
+
export declare function subagentContext(input: SubagentContextInput, budgetBytes: number): Promise<string>;
|