om-memory-system 3.6.0-next.40 → 3.6.0-next.42

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.
@@ -12,6 +12,7 @@ function formatSearchResults(query, results, limit) {
12
12
  results: memoryResults.slice(0, limit || 10).map((result) => ({
13
13
  id: result.id,
14
14
  content: result.memory || result.chunk,
15
+ type: result.type,
15
16
  similarity: Math.round(result.similarity * 100),
16
17
  })),
17
18
  };
@@ -198,6 +199,7 @@ export async function executeMemoryOperation(args, context) {
198
199
  memories: result.memories?.map((memory) => ({
199
200
  id: memory.id,
200
201
  content: memory.summary,
202
+ type: memory.type,
201
203
  createdAt: memory.createdAt,
202
204
  })),
203
205
  };
@@ -1,5 +1,6 @@
1
+ import { realpathSync } from "node:fs";
1
2
  import { readFile } from "node:fs/promises";
2
- import { isAbsolute } from "node:path";
3
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
3
4
  import { CONFIG, refreshConfigIfChanged } from "../config.js";
4
5
  import { captureConversation } from "../core/capture.js";
5
6
  import { classifyCaptureFailure, quickRetryDelayMs } from "../core/capture-retry-policy.js";
@@ -12,6 +13,7 @@ import { log } from "../services/logger.js";
12
13
  import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
13
14
  import { getTags } from "../services/tags.js";
14
15
  import { extractClaudeWindowsAfter, parseClaudeTranscript, } from "./claude-conversation.js";
16
+ import { defaultClaudeProjectsRoot } from "./claude-reader.js";
15
17
  // The web app's side of the Claude Code hooks (design decisions 4, 6, 7):
16
18
  // retrieval for SessionStart and UserPromptSubmit, a one-worker capture queue
17
19
  // for Stop, profile learning, and the once-per-process backfill start. Logs
@@ -61,6 +63,41 @@ function parseRetrieveRequest(body) {
61
63
  prompt: optionalString(input, "prompt"),
62
64
  };
63
65
  }
66
+ /**
67
+ * Resolves symlinks and returns the letter case on disk. Node's plain
68
+ * `realpathSync` keeps the case it is given, which fails the root check on a
69
+ * case-insensitive file system. A path that does not exist yet resolves
70
+ * through its nearest existing parent.
71
+ */
72
+ function realPathOrResolved(path) {
73
+ const absolute = resolve(path);
74
+ try {
75
+ return realpathSync.native(absolute);
76
+ }
77
+ catch {
78
+ const parent = dirname(absolute);
79
+ if (parent === absolute)
80
+ return absolute;
81
+ return join(realPathOrResolved(parent), basename(absolute));
82
+ }
83
+ }
84
+ /**
85
+ * Claude Code writes `<projects root>/<folder>/<session_id>.jsonl`. Accept only
86
+ * that shape, so the route cannot be used to read other `.jsonl` files. Returns
87
+ * the resolved path, which the capture then reads.
88
+ */
89
+ function checkedTranscriptPath(transcriptPath, sessionId) {
90
+ const fileName = `${sessionId}.jsonl`;
91
+ const real = realPathOrResolved(transcriptPath);
92
+ if (basename(transcriptPath) !== fileName || basename(real) !== fileName) {
93
+ throw new ClaudeHookRequestError("transcript_path must be named <session_id>.jsonl");
94
+ }
95
+ const root = realPathOrResolved(defaultClaudeProjectsRoot());
96
+ if (!real.startsWith(root + sep)) {
97
+ throw new ClaudeHookRequestError("transcript_path must be under the Claude projects folder");
98
+ }
99
+ return real;
100
+ }
64
101
  function parseCaptureRequest(body) {
65
102
  const input = objectBody(body);
66
103
  const transcriptPath = input.transcript_path;
@@ -69,9 +106,11 @@ function parseCaptureRequest(body) {
69
106
  !transcriptPath.endsWith(".jsonl")) {
70
107
  throw new ClaudeHookRequestError("transcript_path must be an absolute .jsonl path");
71
108
  }
109
+ const { sessionId, cwd } = sessionAndCwd(input);
72
110
  return {
73
- ...sessionAndCwd(input),
74
- transcriptPath,
111
+ sessionId,
112
+ cwd,
113
+ transcriptPath: checkedTranscriptPath(transcriptPath, sessionId),
75
114
  lastAssistantMessage: optionalString(input, "last_assistant_message"),
76
115
  stopHookActive: input.stop_hook_active === true,
77
116
  };
@@ -16,6 +16,7 @@ import { type ImportPathMap, type ImportProjectReport, type LazyImportSource } f
16
16
  * time.
17
17
  */
18
18
  export declare const CLAUDE_HEADER_READ_LIMIT: number;
19
+ /** `$CLAUDE_CONFIG_DIR/projects` when the variable is set, else `~/.claude/projects`. */
19
20
  export declare function defaultClaudeProjectsRoot(): string;
20
21
  export interface DiscoveredClaudeSession {
21
22
  file: string;
@@ -23,8 +23,10 @@ import { countImportableUnits, projectFilterTag, selectImportWindows, } from "./
23
23
  */
24
24
  export const CLAUDE_HEADER_READ_LIMIT = 1024 * 1024;
25
25
  const READ_CHUNK = 64 * 1024;
26
+ /** `$CLAUDE_CONFIG_DIR/projects` when the variable is set, else `~/.claude/projects`. */
26
27
  export function defaultClaudeProjectsRoot() {
27
- return join(homedir(), ".claude", "projects");
28
+ const configDir = process.env.CLAUDE_CONFIG_DIR?.trim();
29
+ return join(configDir || join(homedir(), ".claude"), "projects");
28
30
  }
29
31
  /** A missing folder is an empty history. Any other read error is reported, not hidden. */
30
32
  function readEntries(dir, unreadable) {
@@ -496,7 +496,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
496
496
  type: "memory",
497
497
  id: r.id,
498
498
  content: r.memory,
499
- memoryType: r.metadata?.type,
499
+ memoryType: r.type ?? r.metadata?.type,
500
500
  tags: r.tags,
501
501
  createdAt: safeToISOString(r.createdAt ?? r.metadata?.createdAt),
502
502
  updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
@@ -64,6 +64,7 @@ export declare class LocalMemoryClient {
64
64
  memories: {
65
65
  id: any;
66
66
  summary: any;
67
+ type: string | undefined;
67
68
  createdAt: string;
68
69
  metadata: any;
69
70
  displayName: any;
@@ -204,6 +204,7 @@ export class LocalMemoryClient {
204
204
  const memories = allMemories.slice(0, limit).map((r) => ({
205
205
  id: r.id,
206
206
  summary: r.content,
207
+ type: r.type ? String(r.type) : undefined,
207
208
  createdAt: safeToISOString(r.created_at),
208
209
  metadata: safeJSONParse(r.metadata),
209
210
  displayName: r.display_name,
@@ -32,6 +32,7 @@ export interface SearchResult {
32
32
  similarity: number;
33
33
  createdAt?: number;
34
34
  tags?: string[];
35
+ type?: string;
35
36
  metadata?: Record<string, unknown>;
36
37
  displayName?: string;
37
38
  userName?: string;
@@ -109,7 +109,7 @@ export class TursoVectorSearch {
109
109
  // hits still get a full hybrid score (missing ANN side is not forced to 0).
110
110
  const rows = await db.all(containerTag === ""
111
111
  ? `
112
- SELECT id, content, tags, created_at, metadata, container_tag,
112
+ SELECT id, content, tags, type, created_at, metadata, container_tag,
113
113
  display_name, user_name, user_email, project_path, project_name,
114
114
  git_repo_url, is_pinned,
115
115
  vector_distance_cos(vector, vector32(?)) AS content_dist,
@@ -120,7 +120,7 @@ export class TursoVectorSearch {
120
120
  WHERE id IN (${placeholders})
121
121
  `
122
122
  : `
123
- SELECT id, content, tags, created_at, metadata, container_tag,
123
+ SELECT id, content, tags, type, created_at, metadata, container_tag,
124
124
  display_name, user_name, user_email, project_path, project_name,
125
125
  git_repo_url, is_pinned,
126
126
  vector_distance_cos(vector, vector32(?)) AS content_dist,
@@ -158,6 +158,7 @@ export class TursoVectorSearch {
158
158
  similarity,
159
159
  createdAt: Number(row.created_at),
160
160
  tags: memoryTagsStr ? memoryTagsStr.split(",") : [],
161
+ type: row.type ? String(row.type) : undefined,
161
162
  metadata: parseMetadata(row.metadata),
162
163
  containerTag: String(row.container_tag),
163
164
  displayName: row.display_name ? String(row.display_name) : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "om-memory-system",
3
- "version": "3.6.0-next.40",
3
+ "version": "3.6.0-next.42",
4
4
  "description": "Opinionated modular memory system for coding agents (OpenCode + Pi)",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",