om-memory-system 3.5.0 → 3.6.0-next.38

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 (54) hide show
  1. package/README.md +24 -16
  2. package/dist/adapters/claude-code/hook-client.d.ts +73 -0
  3. package/dist/adapters/claude-code/hook-client.js +285 -0
  4. package/dist/adapters/claude-code/hook-command.d.ts +6 -0
  5. package/dist/adapters/claude-code/hook-command.js +25 -0
  6. package/dist/cli/index.js +19 -3
  7. package/dist/cli/memory-command.d.ts +24 -0
  8. package/dist/cli/memory-command.js +180 -0
  9. package/dist/core/retrieval.d.ts +21 -0
  10. package/dist/core/retrieval.js +35 -0
  11. package/dist/importer/backfill-controls.d.ts +10 -0
  12. package/dist/importer/backfill-controls.js +40 -11
  13. package/dist/importer/backfill-model.d.ts +1 -1
  14. package/dist/importer/backfill-model.js +3 -0
  15. package/dist/importer/claude-conversation.d.ts +74 -0
  16. package/dist/importer/claude-conversation.js +216 -0
  17. package/dist/importer/claude-hook-api.d.ts +50 -0
  18. package/dist/importer/claude-hook-api.js +441 -0
  19. package/dist/importer/claude-import.d.ts +15 -0
  20. package/dist/importer/claude-import.js +62 -0
  21. package/dist/importer/claude-reader.d.ts +99 -0
  22. package/dist/importer/claude-reader.js +292 -0
  23. package/dist/importer/external-backfill-models.js +2 -2
  24. package/dist/importer/import-args.d.ts +1 -1
  25. package/dist/importer/import-args.js +11 -4
  26. package/dist/importer/import-readiness.d.ts +13 -1
  27. package/dist/importer/import-readiness.js +19 -0
  28. package/dist/importer/import-runs.js +1 -1
  29. package/dist/importer/import-sessions.d.ts +4 -1
  30. package/dist/importer/import-sessions.js +14 -2
  31. package/dist/importer/import-sources.d.ts +8 -2
  32. package/dist/importer/import-sources.js +21 -4
  33. package/dist/importer/map-suggestions.d.ts +1 -0
  34. package/dist/importer/map-suggestions.js +6 -1
  35. package/dist/importer/run-import.js +14 -10
  36. package/dist/importer/web-import-jobs.js +10 -3
  37. package/dist/index.js +9 -23
  38. package/dist/services/ai/live-model-choice.d.ts +5 -0
  39. package/dist/services/ai/live-model-choice.js +7 -0
  40. package/dist/services/backfill-state.d.ts +1 -1
  41. package/dist/services/capture-retry-queue.js +3 -2
  42. package/dist/services/claude-capture-cursor.d.ts +10 -0
  43. package/dist/services/claude-capture-cursor.js +44 -0
  44. package/dist/services/settings-snapshot.d.ts +1 -0
  45. package/dist/services/settings-snapshot.js +3 -1
  46. package/dist/services/web-server.d.ts +2 -0
  47. package/dist/services/web-server.js +61 -28
  48. package/dist/types/host-label.d.ts +3 -0
  49. package/dist/types/host-label.js +9 -0
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/web/assets/index-BFiVixOK.js +124 -0
  52. package/dist/web/index.html +1 -1
  53. package/package.json +1 -1
  54. package/dist/web/assets/index-DIy--9g_.js +0 -124
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Entry types seen in real transcripts. A line with any other type is skipped
3
+ * and counted, so a new Claude Code version shows up in the import report.
4
+ */
5
+ const KNOWN_ENTRY_TYPES = new Set([
6
+ "user",
7
+ "assistant",
8
+ "system",
9
+ "attachment",
10
+ "summary",
11
+ "file-history-snapshot",
12
+ "file-history-delta",
13
+ "last-prompt",
14
+ "mode",
15
+ "permission-mode",
16
+ "ai-title",
17
+ "custom-title",
18
+ "pr-link",
19
+ "queue-operation",
20
+ "cost-state",
21
+ "agent-name",
22
+ "atis-latch",
23
+ ]);
24
+ /**
25
+ * User entries that Claude Code writes itself: local command output, bash
26
+ * mode, and background task notices. They end the current turn but never
27
+ * start one.
28
+ */
29
+ const NOISE_PREFIXES = [
30
+ "<local-command-stdout>",
31
+ "<local-command-stderr>",
32
+ "<local-command-caveat>",
33
+ "<bash-input>",
34
+ "<bash-stdout>",
35
+ "<bash-stderr>",
36
+ "<task-notification>",
37
+ ];
38
+ const MAX_TOOL_INPUT_LENGTH = 100;
39
+ /** Parse one transcript line. A JSON value without a string `type` is unreadable. */
40
+ export function parseClaudeTranscriptLine(line) {
41
+ if (!line.trim())
42
+ return { status: "blank" };
43
+ let value;
44
+ try {
45
+ value = JSON.parse(line);
46
+ }
47
+ catch {
48
+ return { status: "unreadable" };
49
+ }
50
+ if (!value || typeof value !== "object" || Array.isArray(value))
51
+ return { status: "unreadable" };
52
+ const type = value.type;
53
+ if (typeof type !== "string")
54
+ return { status: "unreadable" };
55
+ if (!KNOWN_ENTRY_TYPES.has(type))
56
+ return { status: "unknown-type" };
57
+ return { status: "entry", entry: value };
58
+ }
59
+ /** Parse a whole transcript, counting the lines that were skipped. */
60
+ export function parseClaudeTranscript(text) {
61
+ const result = { entries: [], unreadableLines: 0, unknownTypes: 0 };
62
+ for (const line of text.split("\n")) {
63
+ const parsed = parseClaudeTranscriptLine(line);
64
+ if (parsed.status === "entry")
65
+ result.entries.push(parsed.entry);
66
+ else if (parsed.status === "unreadable")
67
+ result.unreadableLines++;
68
+ else if (parsed.status === "unknown-type")
69
+ result.unknownTypes++;
70
+ }
71
+ return result;
72
+ }
73
+ function blocksOf(entry) {
74
+ const content = entry.message?.content;
75
+ return Array.isArray(content)
76
+ ? content.filter((block) => block && typeof block === "object")
77
+ : [];
78
+ }
79
+ function promptText(entry) {
80
+ const content = entry.message?.content;
81
+ if (typeof content === "string")
82
+ return content.trim();
83
+ return blocksOf(entry)
84
+ .filter((block) => block.type === "text" && typeof block.text === "string")
85
+ .map((block) => block.text)
86
+ .join("\n")
87
+ .trim();
88
+ }
89
+ /**
90
+ * A user entry that ends the current turn: in the main chain, not written by
91
+ * Claude Code as context (`isMeta`), and not a tool result.
92
+ */
93
+ function isTurnBoundary(entry) {
94
+ if (entry.type !== "user" || entry.isMeta === true || entry.isSidechain === true)
95
+ return false;
96
+ return !blocksOf(entry).some((block) => block.type === "tool_result");
97
+ }
98
+ /** Show a slash command as the user typed it: `/name args`. */
99
+ function slashCommandText(text) {
100
+ const name = /<command-name>([^<]*)<\/command-name>/.exec(text)?.[1]?.trim();
101
+ if (!name)
102
+ return null;
103
+ const args = /<command-args>([^<]*)<\/command-args>/.exec(text)?.[1]?.trim();
104
+ return args ? `${name} ${args}` : name;
105
+ }
106
+ /** The prompt a boundary entry starts, or null for Claude Code's own noise. */
107
+ function turnPrompt(entry) {
108
+ const text = promptText(entry);
109
+ if (!text)
110
+ return null;
111
+ if (NOISE_PREFIXES.some((prefix) => text.startsWith(prefix)))
112
+ return null;
113
+ return slashCommandText(text) ?? text;
114
+ }
115
+ function formatToolInput(input) {
116
+ if (input === undefined || input === null)
117
+ return "";
118
+ if (typeof input !== "object" || Array.isArray(input))
119
+ return JSON.stringify(input);
120
+ return Object.entries(input)
121
+ .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
122
+ .join(", ");
123
+ }
124
+ function boundToolInput(input) {
125
+ if (input.length > MAX_TOOL_INPUT_LENGTH) {
126
+ return input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
127
+ }
128
+ return input;
129
+ }
130
+ function closeWindow(open) {
131
+ if (open.textResponses.length === 0 && open.toolCalls.length === 0)
132
+ return null;
133
+ const timestamp = Date.parse(open.entry.timestamp ?? "");
134
+ return {
135
+ userEntryId: open.entry.uuid,
136
+ userPrompt: open.userPrompt,
137
+ textResponses: open.textResponses,
138
+ toolCalls: open.toolCalls,
139
+ sourceEntryIds: open.sourceEntryIds,
140
+ cwd: typeof open.entry.cwd === "string" ? open.entry.cwd : null,
141
+ ...(Number.isNaN(timestamp) ? {} : { userTimestamp: timestamp, sourceTimestamp: timestamp }),
142
+ };
143
+ }
144
+ function addAssistantWork(open, entry) {
145
+ if (typeof entry.uuid === "string")
146
+ open.sourceEntryIds.push(entry.uuid);
147
+ const blocks = blocksOf(entry);
148
+ const text = blocks
149
+ .filter((block) => block.type === "text" && typeof block.text === "string" && block.text.trim())
150
+ .map((block) => block.text)
151
+ .join("\n")
152
+ .trim();
153
+ if (text)
154
+ open.textResponses.push(text);
155
+ for (const block of blocks.filter((item) => item.type === "tool_use")) {
156
+ open.toolCalls.push({
157
+ name: typeof block.name === "string" ? block.name : "unknown",
158
+ input: boundToolInput(formatToolInput(block.input)),
159
+ });
160
+ }
161
+ }
162
+ function windowsWithIndex(entries) {
163
+ const result = [];
164
+ let open = null;
165
+ const flush = () => {
166
+ const window = open ? closeWindow(open) : null;
167
+ if (open && window)
168
+ result.push({ entryIndex: open.entryIndex, window });
169
+ open = null;
170
+ };
171
+ entries.forEach((entry, entryIndex) => {
172
+ if (isTurnBoundary(entry)) {
173
+ flush();
174
+ const userPrompt = turnPrompt(entry);
175
+ if (userPrompt && typeof entry.uuid === "string") {
176
+ open = {
177
+ entryIndex,
178
+ entry,
179
+ userPrompt,
180
+ textResponses: [],
181
+ toolCalls: [],
182
+ sourceEntryIds: [entry.uuid],
183
+ };
184
+ }
185
+ return;
186
+ }
187
+ if (open && entry.type === "assistant" && entry.isSidechain !== true) {
188
+ addAssistantWork(open, entry);
189
+ }
190
+ });
191
+ flush();
192
+ return result;
193
+ }
194
+ /**
195
+ * All capture windows in a transcript, oldest first: one per user prompt that
196
+ * produced assistant text or tool work. Sidechain (subagent) entries, meta
197
+ * entries, and tool results are skipped; tool results and meta entries do not
198
+ * end a turn. Thinking blocks are never included. Tool inputs are bounded by
199
+ * the same truncation policy as the other hosts.
200
+ */
201
+ export function extractClaudeConversationWindows(entries) {
202
+ return windowsWithIndex(entries).map((item) => item.window);
203
+ }
204
+ /**
205
+ * Windows for live capture: every window whose prompt comes after the cursor
206
+ * entry. Without a cursor, or when the cursor is no longer in the transcript,
207
+ * only the last window is returned, so a lost cursor never re-captures a
208
+ * whole session.
209
+ */
210
+ export function extractClaudeWindowsAfter(entries, cursorUserUuid) {
211
+ const windows = windowsWithIndex(entries);
212
+ const cursorIndex = cursorUserUuid === null ? -1 : entries.findIndex((entry) => entry.uuid === cursorUserUuid);
213
+ if (cursorIndex === -1)
214
+ return windows.slice(-1).map((item) => item.window);
215
+ return windows.filter((item) => item.entryIndex > cursorIndex).map((item) => item.window);
216
+ }
@@ -0,0 +1,50 @@
1
+ import type { CaptureSummaryProvider } from "../core/host.js";
2
+ import type { ModelPort } from "../core/profile-analysis.js";
3
+ import { type ClaudeConversationWindow, type ClaudeTranscriptEntry } from "./claude-conversation.js";
4
+ export interface ClaudeHookDeps {
5
+ /** The capture model. Defaults to the external API. */
6
+ captureProvider?: () => CaptureSummaryProvider;
7
+ /** The profile learning model. Defaults to the external API. */
8
+ profileModel?: () => ModelPort;
9
+ /** Starts the Claude Code backfill after the start-up delay; the web app passes its controls. */
10
+ startBackfill?: () => Promise<unknown>;
11
+ sleep?: (ms: number) => Promise<void>;
12
+ }
13
+ /** A hook request the handler cannot use. `status` is the HTTP status. */
14
+ export declare class ClaudeHookRequestError extends Error {
15
+ readonly status = 400;
16
+ constructor(message: string);
17
+ }
18
+ /**
19
+ * `POST /api/claude/retrieve`: recent project memories for a fresh session,
20
+ * the session's own memories after compaction or resume, or the retrieval
21
+ * section for a prompt. Every text is wrapped in the retrieval tag; an empty
22
+ * string means nothing to add.
23
+ */
24
+ export declare function handleClaudeRetrieve(body: unknown, deps?: ClaudeHookDeps): Promise<{
25
+ additionalContext: string;
26
+ }>;
27
+ /**
28
+ * `POST /api/claude/capture`: check the request, queue the turn on the
29
+ * one-worker queue, and answer at once. Throws `ClaudeHookRequestError` for a
30
+ * request that cannot be used.
31
+ */
32
+ export declare function handleClaudeCapture(body: unknown, deps?: ClaudeHookDeps): {
33
+ queued: true;
34
+ };
35
+ /** Resolves when every queued capture has finished. */
36
+ export declare function whenClaudeCaptureIdle(): Promise<void>;
37
+ /**
38
+ * The windows after the cursor. The transcript file can lag the hook, so the
39
+ * hook's final text is added as a last assistant reply when the transcript
40
+ * does not already end with it. It carries no entry id, so `sourceEntryIds`
41
+ * stay the reader's ids.
42
+ */
43
+ export declare function claudeWindowsToCapture(entries: ClaudeTranscriptEntry[], cursor: string | null, finalText: string | undefined): ClaudeConversationWindow[];
44
+ /**
45
+ * Register the Claude Code retry drain in this web app and start one retry
46
+ * pass. The drain uses the external API. When capture is off, the first
47
+ * capture request logs the missing settings, so a web app that never serves
48
+ * Claude Code does not log them.
49
+ */
50
+ export declare function startClaudeCodeWorker(): void;