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.
Files changed (69) hide show
  1. package/README.md +4 -0
  2. package/dist/adapters/claude-code/hook-client.d.ts +1 -1
  3. package/dist/adapters/claude-code/hook-client.js +15 -2
  4. package/dist/adapters/opencode/in-process-client.d.ts +2 -0
  5. package/dist/adapters/opencode/in-process-client.js +16 -0
  6. package/dist/adapters/opencode/opencode-host-config.d.ts +3 -0
  7. package/dist/adapters/opencode/opencode-host-config.js +2 -0
  8. package/dist/adapters/opencode/tui-recall.d.ts +68 -0
  9. package/dist/adapters/opencode/tui-recall.js +312 -0
  10. package/dist/adapters/pi/extension.d.ts +1 -0
  11. package/dist/adapters/pi/extension.js +28 -11
  12. package/dist/adapters/pi/recall-rows.d.ts +31 -0
  13. package/dist/adapters/pi/recall-rows.js +148 -0
  14. package/dist/adapters/pi/session-context.d.ts +22 -0
  15. package/dist/adapters/pi/session-context.js +125 -0
  16. package/dist/config-template.d.ts +1 -1
  17. package/dist/config-template.js +20 -0
  18. package/dist/config.d.ts +8 -0
  19. package/dist/config.js +8 -4
  20. package/dist/core/context-budget.d.ts +2 -0
  21. package/dist/core/context-budget.js +2 -1
  22. package/dist/core/opening-context.d.ts +45 -0
  23. package/dist/core/opening-context.js +60 -0
  24. package/dist/core/recall-summary.d.ts +32 -0
  25. package/dist/core/recall-summary.js +77 -0
  26. package/dist/core/retrieval.d.ts +16 -0
  27. package/dist/core/retrieval.js +33 -11
  28. package/dist/importer/claude-hook-api.d.ts +5 -4
  29. package/dist/importer/claude-hook-api.js +58 -41
  30. package/dist/importer/claude-hook-context.d.ts +49 -0
  31. package/dist/importer/claude-hook-context.js +264 -0
  32. package/dist/index.js +1 -1
  33. package/dist/services/ai/local-api-url.d.ts +2 -0
  34. package/dist/services/ai/local-api-url.js +12 -0
  35. package/dist/services/ai/provider-config.d.ts +2 -0
  36. package/dist/services/ai/provider-config.js +3 -1
  37. package/dist/services/ai/providers/google-gemini.js +2 -1
  38. package/dist/services/ai/providers/openai-responses.js +2 -1
  39. package/dist/services/chat/chat-runtime.js +5 -1
  40. package/dist/services/chat/chat-status.js +4 -1
  41. package/dist/services/global-config-writer.js +39 -14
  42. package/dist/services/injection-options-config.d.ts +53 -0
  43. package/dist/services/injection-options-config.js +56 -0
  44. package/dist/services/settings-snapshot.js +44 -12
  45. package/dist/services/single-flight.d.ts +2 -0
  46. package/dist/services/single-flight.js +12 -0
  47. package/dist/services/visible-recall-config.d.ts +39 -0
  48. package/dist/services/visible-recall-config.js +28 -0
  49. package/dist/services/web-server.d.ts +3 -0
  50. package/dist/services/web-server.js +26 -8
  51. package/dist/utils/memory-limits.d.ts +2 -1
  52. package/dist/utils/memory-limits.js +6 -3
  53. package/dist/v2/adapter.d.ts +2 -0
  54. package/dist/v2/adapter.js +110 -31
  55. package/dist/v2/legacy-client.js +2 -1
  56. package/dist/v2/memory-bridge.d.ts +15 -0
  57. package/dist/v2/memory-bridge.js +21 -0
  58. package/dist/v2/recall-reporter.d.ts +27 -0
  59. package/dist/v2/recall-reporter.js +105 -0
  60. package/dist/v2/recall-rpc.d.ts +51 -0
  61. package/dist/v2/recall-rpc.js +38 -0
  62. package/dist/v2/session-parent.d.ts +14 -0
  63. package/dist/v2/session-parent.js +26 -0
  64. package/dist/web/assets/{index-CJ_K-hMG.js → index-BhQR88tU.js} +16 -16
  65. package/dist/web/assets/index-BmFUZbGr.css +2 -0
  66. package/dist/web/index.html +2 -2
  67. package/opencode/tui.tsx +22 -3
  68. package/package.json +1 -1
  69. 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
+ }
@@ -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
@@ -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
- * Prompt-aware semantic retrieval shared by every host (Pi `before_agent_start`,
10
- * OpenCode v2 `prompt`/`context` hooks): search the shared project memory with
11
- * the incoming prompt and format a bounded context section (memory + user
12
- * profile). Returns null when nothing relevant is found.
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 buildRetrievalSection(prompt, directory, sessionId, budget) {
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 tags = getTags(directory);
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
- if (results.length === 0)
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`: recent project memories for a fresh session,
19
- * the session's own memories after compaction or resume, or the retrieval
20
- * section for a prompt. Every text is wrapped in the retrieval tag; an empty
21
- * string means nothing to add.
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 { buildRecentMemoriesSection, buildRetrievalSection, formatMemoriesForCompaction, retrievalWrapperBytes, wrapRetrievalSection, } from "../core/retrieval.js";
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 UserPromptSubmit, a one-worker capture queue
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
- if (input.event !== "session-start" && input.event !== "user-prompt-submit") {
61
- throw new ClaudeHookRequestError("event must be session-start or user-prompt-submit");
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: input.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
- async function retrievalSection(request, budgetBytes) {
144
- // Every section this api returns is wrapped in the retrieval tag, so each
145
- // packed section reserves the wrapper bytes.
146
- const budget = { maxBytes: budgetBytes, wrapperBytes: retrievalWrapperBytes() };
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
- const prompt = stripPrivateContent(request.prompt ?? "").trim();
149
- if (!CONFIG.chatMessage.enabled || !prompt || isFullyPrivate(request.prompt ?? "")) {
150
- return null;
151
- }
152
- return buildRetrievalSection(prompt, request.cwd, request.sessionId, budget);
162
+ return promptContext({
163
+ prompt: request.prompt ?? "",
164
+ cwd: request.cwd,
165
+ sessionId: request.sessionId,
166
+ transcriptPath: requestTranscript(request),
167
+ }, limits);
153
168
  }
154
- const tags = getTags(request.cwd);
155
- if (request.source === "compact" || request.source === "resume") {
156
- if (!CONFIG.compaction.enabled)
157
- return null;
158
- const result = await memoryClient.searchMemoriesBySessionID(request.sessionId, tags.project.tag, CONFIG.compaction.memoryLimit);
159
- const own = result.success
160
- ? result.results.filter((memory) => memory.metadata?.host === HOST)
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
- if (!CONFIG.chatMessage.enabled)
165
- return null;
166
- return buildRecentMemoriesSection({
167
- projectTag: tags.project.tag,
168
- userEmail: tags.user.userEmail,
169
- sessionId: request.sessionId,
170
- maxMemories: CONFIG.chatMessage.maxMemories,
171
- excludeCurrentSession: CONFIG.chatMessage.excludeCurrentSession,
172
- maxAgeDays: CONFIG.chatMessage.maxAgeDays,
173
- budget,
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`: recent project memories for a fresh session,
178
- * the session's own memories after compaction or resume, or the retrieval
179
- * section for a prompt. Every text is wrapped in the retrieval tag; an empty
180
- * string means nothing to add.
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
- const section = await retrievalSection(request, budgetBytes);
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>;