@hyperspell/openclaw-hyperspell 0.8.1 → 0.10.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/client.ts CHANGED
@@ -37,6 +37,25 @@ export type Connection = {
37
37
  provider: HyperspellSource;
38
38
  };
39
39
 
40
+ export type EmotionalStateResponse = {
41
+ resourceId: string
42
+ summary: string
43
+ extractedAt: string
44
+ sessionId: string | null
45
+ relationshipId: string | null
46
+ status: string
47
+ }
48
+
49
+ export type EmotionalStateLatest = {
50
+ resourceId: string
51
+ summary: string
52
+ extractedAt: string
53
+ sessionId: string | null
54
+ relationshipId: string | null
55
+ }
56
+
57
+ const API_BASE_URL = "https://api.hyperspell.com"
58
+
40
59
  export class HyperspellClient {
41
60
  private client: Hyperspell;
42
61
  private config: HyperspellConfig;
@@ -52,29 +71,63 @@ export class HyperspellClient {
52
71
  );
53
72
  }
54
73
 
74
+ private rawHeaders(): Record<string, string> {
75
+ const headers: Record<string, string> = {
76
+ "Content-Type": "application/json",
77
+ Authorization: `Bearer ${this.config.apiKey}`,
78
+ };
79
+ if (this.config.userId) {
80
+ headers["X-As-User"] = this.config.userId;
81
+ }
82
+ return headers;
83
+ }
84
+
85
+ private requestOptions(userId?: string) {
86
+ if (!userId) return undefined;
87
+ return { headers: { "X-As-User": userId } };
88
+ }
89
+
55
90
  async search(
56
91
  query: string,
57
- options?: { limit?: number; sources?: HyperspellSource[]; after?: string; before?: string },
92
+ options?: {
93
+ limit?: number;
94
+ sources?: HyperspellSource[];
95
+ after?: string;
96
+ before?: string;
97
+ userId?: string;
98
+ },
58
99
  ): Promise<SearchResult[]> {
59
100
  const limit = options?.limit ?? this.config.maxResults;
60
101
  const sources =
61
102
  options?.sources ??
62
103
  (this.config.sources.length > 0 ? this.config.sources : undefined);
63
104
 
64
- log.debugRequest("memories.search", { query, limit, sources, after: options?.after, before: options?.before });
65
-
66
- const response = await this.client.memories.search({
105
+ log.debugRequest("memories.search", {
67
106
  query,
107
+ limit,
68
108
  sources,
69
- options: {
70
- max_results: limit,
71
- ...(options?.after ? { after: options.after } : {}),
72
- ...(options?.before ? { before: options.before } : {}),
73
- },
109
+ after: options?.after,
110
+ before: options?.before,
111
+ userId: options?.userId,
74
112
  });
75
113
 
114
+ const response = await this.client.memories.search(
115
+ {
116
+ query,
117
+ sources,
118
+ options: {
119
+ max_results: limit,
120
+ ...(options?.after ? { after: options.after } : {}),
121
+ ...(options?.before ? { before: options.before } : {}),
122
+ },
123
+ },
124
+ this.requestOptions(options?.userId),
125
+ );
126
+
76
127
  const results: SearchResult[] = response.documents.map((doc) => {
77
- const raw = doc as typeof doc & { highlights?: Array<{ id: string; score: number; text: string }> };
128
+ const raw = doc as typeof doc & {
129
+ highlights?: Array<{ id: string; score: number; text: string }>;
130
+ };
78
131
  return {
79
132
  resourceId: doc.resource_id,
80
133
  title: doc.title ?? null,
@@ -82,7 +135,11 @@ export class HyperspellClient {
82
135
  score: doc.score ?? null,
83
136
  url: (doc.metadata?.url as string | null) ?? null,
84
137
  createdAt: (doc.metadata?.created_at as string | null) ?? null,
85
- highlights: (raw.highlights ?? []).map((h) => ({ id: h.id, score: h.score, text: h.text })),
138
+ highlights: (raw.highlights ?? []).map((h) => ({
139
+ id: h.id,
140
+ score: h.score,
141
+ text: h.text,
142
+ })),
86
143
  };
87
144
  });
88
145
 
@@ -92,25 +149,41 @@ export class HyperspellClient {
92
149
 
93
150
  async searchRaw(
94
151
  query: string,
95
- options?: { limit?: number; sources?: HyperspellSource[]; after?: string; before?: string },
152
+ options?: {
153
+ limit?: number;
154
+ sources?: HyperspellSource[];
155
+ after?: string;
156
+ before?: string;
157
+ userId?: string;
158
+ },
96
159
  ): Promise<Record<string, unknown>> {
97
160
  const limit = options?.limit ?? this.config.maxResults;
98
161
  const sources =
99
162
  options?.sources ??
100
163
  (this.config.sources.length > 0 ? this.config.sources : undefined);
101
164
 
102
- log.debugRequest("memories.search (raw)", { query, limit, sources, after: options?.after, before: options?.before });
103
-
104
- const response = await this.client.memories.search({
165
+ log.debugRequest("memories.search (raw)", {
105
166
  query,
167
+ limit,
106
168
  sources,
107
- options: {
108
- max_results: limit,
109
- ...(options?.after ? { after: options.after } : {}),
110
- ...(options?.before ? { before: options.before } : {}),
111
- },
169
+ after: options?.after,
170
+ before: options?.before,
171
+ userId: options?.userId,
112
172
  });
113
173
 
174
+ const response = await this.client.memories.search(
175
+ {
176
+ query,
177
+ sources,
178
+ options: {
179
+ max_results: limit,
180
+ ...(options?.after ? { after: options.after } : {}),
181
+ ...(options?.before ? { before: options.before } : {}),
182
+ },
183
+ },
184
+ this.requestOptions(options?.userId),
185
+ );
186
+
114
187
  log.debugResponse("memories.search (raw)", {
115
188
  count: response.documents.length,
116
189
  });
@@ -120,7 +193,11 @@ export class HyperspellClient {
120
193
 
121
194
  async searchWithAnswer(
122
195
  query: string,
123
- options?: { limit?: number; sources?: HyperspellSource[] },
196
+ options?: {
197
+ limit?: number;
198
+ sources?: HyperspellSource[];
199
+ userId?: string;
200
+ },
124
201
  ): Promise<SearchWithAnswerResult> {
125
202
  const limit = options?.limit ?? this.config.maxResults;
126
203
  const sources =
@@ -131,16 +208,20 @@ export class HyperspellClient {
131
208
  query,
132
209
  limit,
133
210
  sources,
211
+ userId: options?.userId,
134
212
  });
135
213
 
136
- const response = await this.client.memories.search({
137
- query,
138
- sources,
139
- answer: true,
140
- options: {
141
- max_results: limit,
214
+ const response = await this.client.memories.search(
215
+ {
216
+ query,
217
+ sources,
218
+ answer: true,
219
+ options: {
220
+ max_results: limit,
221
+ },
142
222
  },
143
- });
223
+ this.requestOptions(options?.userId),
224
+ );
144
225
 
145
226
  const documents: SearchResult[] = response.documents.map((doc) => ({
146
227
  resourceId: doc.resource_id,
@@ -171,6 +252,7 @@ export class HyperspellClient {
171
252
  collection?: string;
172
253
  date?: string;
173
254
  metadata?: Record<string, string | number | boolean>;
255
+ userId?: string;
174
256
  },
175
257
  ): Promise<{ resourceId: string }> {
176
258
  log.debugRequest("memories.add", {
@@ -179,19 +261,23 @@ export class HyperspellClient {
179
261
  resourceId: options?.resourceId,
180
262
  collection: options?.collection,
181
263
  date: options?.date,
264
+ userId: options?.userId,
182
265
  });
183
266
 
184
- const result = await this.client.memories.add({
185
- text,
186
- title: options?.title,
187
- resource_id: options?.resourceId,
188
- collection: options?.collection,
189
- date: options?.date,
190
- metadata: {
191
- ...options?.metadata,
192
- openclaw_source: "command",
267
+ const result = await this.client.memories.add(
268
+ {
269
+ text,
270
+ title: options?.title,
271
+ resource_id: options?.resourceId,
272
+ collection: options?.collection,
273
+ date: options?.date,
274
+ metadata: {
275
+ ...options?.metadata,
276
+ openclaw_source: "command",
277
+ },
193
278
  },
194
- });
279
+ this.requestOptions(options?.userId),
280
+ );
195
281
 
196
282
  log.debugResponse("memories.add", { resourceId: result.resource_id });
197
283
  return { resourceId: result.resource_id };
@@ -215,10 +301,15 @@ export class HyperspellClient {
215
301
 
216
302
  async getConnectUrl(
217
303
  integrationId: string,
304
+ options?: { userId?: string },
218
305
  ): Promise<{ url: string; expiresAt: string }> {
219
306
  log.debugRequest("integrations.connect", { integrationId });
220
307
 
221
- const response = await this.client.integrations.connect(integrationId);
308
+ const response = await this.client.integrations.connect(
309
+ integrationId,
310
+ undefined,
311
+ this.requestOptions(options?.userId),
312
+ );
222
313
 
223
314
  log.debugResponse("integrations.connect", { url: response.url });
224
315
  return {
@@ -231,6 +322,7 @@ export class HyperspellClient {
231
322
  source?: HyperspellSource;
232
323
  collection?: string;
233
324
  pageSize?: number;
325
+ userId?: string;
234
326
  }): AsyncGenerator<{
235
327
  resourceId: string;
236
328
  source: HyperspellSource;
@@ -240,6 +332,7 @@ export class HyperspellClient {
240
332
  log.debugRequest("memories.list", {
241
333
  source: options?.source,
242
334
  collection: options?.collection,
335
+ userId: options?.userId,
243
336
  });
244
337
 
245
338
  const params: Record<string, unknown> = {
@@ -248,7 +341,10 @@ export class HyperspellClient {
248
341
  if (options?.source) params.source = options.source;
249
342
  if (options?.collection) params.collection = options.collection;
250
343
 
251
- for await (const memory of this.client.memories.list(params as any)) {
344
+ for await (const memory of this.client.memories.list(
345
+ params as any,
346
+ this.requestOptions(options?.userId),
347
+ )) {
252
348
  yield {
253
349
  resourceId: memory.resource_id,
254
350
  source: memory.source as HyperspellSource,
@@ -261,10 +357,15 @@ export class HyperspellClient {
261
357
  async getMemory(
262
358
  resourceId: string,
263
359
  source: HyperspellSource,
360
+ options?: { userId?: string },
264
361
  ): Promise<Record<string, unknown>> {
265
362
  log.debugRequest("memories.get", { resourceId, source });
266
363
 
267
- const response = await this.client.memories.get(resourceId, { source });
364
+ const response = await this.client.memories.get(
365
+ resourceId,
366
+ { source },
367
+ this.requestOptions(options?.userId),
368
+ );
268
369
  const raw = response as unknown as Record<string, unknown>;
269
370
 
270
371
  log.debugResponse("memories.get", { resourceId, hasData: "data" in raw });
@@ -278,30 +379,35 @@ export class HyperspellClient {
278
379
  title?: string;
279
380
  extract?: Array<"procedure" | "memory" | "mood">;
280
381
  metadata?: Record<string, string | number | boolean>;
382
+ userId?: string;
281
383
  },
282
384
  ): Promise<{ resourceId: string; status: string }> {
283
385
  log.debugRequest("sessions.add", {
284
386
  historyLength: history.length,
285
387
  sessionId: options?.sessionId,
286
388
  extract: options?.extract,
389
+ userId: options?.userId,
287
390
  });
288
391
 
289
- const result = await this.client.sessions.add({
290
- history,
291
- session_id: options?.sessionId,
292
- title: options?.title,
293
- format: "openclaw",
294
- // Cast: SDK 0.35 typing accepts only ["procedure" | "memory"], but the
295
- // backend's mood extractor (hyperspell/hyperspell#581) accepts "mood".
296
- // Remove this cast once the OpenAPI spec is updated.
297
- extract: (options?.extract ?? ["procedure"]) as Array<
298
- "procedure" | "memory"
299
- >,
300
- metadata: {
301
- ...options?.metadata,
302
- openclaw_source: "agent_end",
392
+ const result = await this.client.sessions.add(
393
+ {
394
+ history,
395
+ session_id: options?.sessionId,
396
+ title: options?.title,
397
+ format: "openclaw",
398
+ // Cast: SDK 0.35 typing accepts only ["procedure" | "memory"], but the
399
+ // backend's mood extractor (hyperspell/hyperspell#581) accepts "mood".
400
+ // Remove this cast once the OpenAPI spec is updated.
401
+ extract: (options?.extract ?? ["procedure"]) as Array<
402
+ "procedure" | "memory"
403
+ >,
404
+ metadata: {
405
+ ...options?.metadata,
406
+ openclaw_source: "agent_end",
407
+ },
303
408
  },
304
- });
409
+ this.requestOptions(options?.userId),
410
+ );
305
411
 
306
412
  log.debugResponse("sessions.add", {
307
413
  resourceId: result.resource_id,
@@ -310,10 +416,14 @@ export class HyperspellClient {
310
416
  return { resourceId: result.resource_id, status: result.status };
311
417
  }
312
418
 
313
- async listConnections(): Promise<Connection[]> {
314
- log.debugRequest("connections.list", {});
419
+ async listConnections(options?: {
420
+ userId?: string;
421
+ }): Promise<Connection[]> {
422
+ log.debugRequest("connections.list", { userId: options?.userId });
315
423
 
316
- const response = await this.client.connections.list();
424
+ const response = await this.client.connections.list(
425
+ this.requestOptions(options?.userId),
426
+ );
317
427
 
318
428
  const connections: Connection[] = response.connections.map((conn) => ({
319
429
  id: conn.id,
@@ -325,4 +435,106 @@ export class HyperspellClient {
325
435
  log.debugResponse("connections.list", { count: connections.length });
326
436
  return connections;
327
437
  }
438
+
439
+ // -- Emotional State (raw fetch -- not in public SDK) -----------------------
440
+
441
+ async storeEmotionalState(
442
+ conversation: string,
443
+ options?: {
444
+ sessionId?: string;
445
+ relationshipId?: string;
446
+ metadata?: Record<string, string | number | boolean>;
447
+ },
448
+ ): Promise<EmotionalStateResponse> {
449
+ log.debugRequest("emotional-state.store", {
450
+ conversationLength: conversation.length,
451
+ relationshipId: options?.relationshipId,
452
+ });
453
+
454
+ const body: Record<string, unknown> = { conversation };
455
+ if (options?.sessionId) body.session_id = options.sessionId;
456
+ if (options?.relationshipId) body.relationship_id = options.relationshipId;
457
+ if (options?.metadata) body.metadata = options.metadata;
458
+
459
+ const res = await fetch(`${API_BASE_URL}/emotional-state`, {
460
+ method: "POST",
461
+ headers: this.rawHeaders(),
462
+ body: JSON.stringify(body),
463
+ });
464
+
465
+ if (!res.ok) {
466
+ const text = await res.text().catch(() => "");
467
+ throw new Error(`POST /emotional-state failed (${res.status}): ${text}`);
468
+ }
469
+
470
+ const data = await res.json();
471
+ const result: EmotionalStateResponse = {
472
+ resourceId: data.resource_id,
473
+ summary: data.summary,
474
+ extractedAt: data.extracted_at,
475
+ sessionId: data.session_id ?? null,
476
+ relationshipId: data.relationship_id ?? null,
477
+ status: data.status,
478
+ };
479
+
480
+ log.debugResponse("emotional-state.store", { resourceId: result.resourceId });
481
+ return result;
482
+ }
483
+
484
+ async getEmotionalState(relationshipId?: string): Promise<EmotionalStateLatest | null> {
485
+ log.debugRequest("emotional-state.get", { relationshipId });
486
+
487
+ const url = new URL(`${API_BASE_URL}/emotional-state`);
488
+ if (relationshipId) url.searchParams.set("relationship_id", relationshipId);
489
+
490
+ const res = await fetch(url.toString(), {
491
+ method: "GET",
492
+ headers: this.rawHeaders(),
493
+ });
494
+
495
+ if (!res.ok) {
496
+ const text = await res.text().catch(() => "");
497
+ throw new Error(`GET /emotional-state failed (${res.status}): ${text}`);
498
+ }
499
+
500
+ const data = await res.json();
501
+ if (data === null) {
502
+ log.debugResponse("emotional-state.get", { found: false });
503
+ return null;
504
+ }
505
+
506
+ const result: EmotionalStateLatest = {
507
+ resourceId: data.resource_id,
508
+ summary: data.summary,
509
+ extractedAt: data.extracted_at,
510
+ sessionId: data.session_id ?? null,
511
+ relationshipId: data.relationship_id ?? null,
512
+ };
513
+
514
+ log.debugResponse("emotional-state.get", { found: true, resourceId: result.resourceId });
515
+ return result;
516
+ }
517
+
518
+ async deleteEmotionalState(relationshipId?: string): Promise<{ deletedCount: number }> {
519
+ log.debugRequest("emotional-state.delete", { relationshipId });
520
+
521
+ const url = new URL(`${API_BASE_URL}/emotional-state`);
522
+ if (relationshipId) url.searchParams.set("relationship_id", relationshipId);
523
+
524
+ const res = await fetch(url.toString(), {
525
+ method: "DELETE",
526
+ headers: this.rawHeaders(),
527
+ });
528
+
529
+ if (!res.ok) {
530
+ const text = await res.text().catch(() => "");
531
+ throw new Error(`DELETE /emotional-state failed (${res.status}): ${text}`);
532
+ }
533
+
534
+ const data = await res.json();
535
+ const result = { deletedCount: data.deleted_count };
536
+
537
+ log.debugResponse("emotional-state.delete", result);
538
+ return result;
539
+ }
328
540
  }
package/commands/setup.ts CHANGED
@@ -333,6 +333,7 @@ async function runSetup(): Promise<void> {
333
333
  userId,
334
334
  autoContext: true,
335
335
  autoTrace: { enabled: false, extract: ["procedure"] },
336
+ emotionalContext: false,
336
337
  syncMemories: true,
337
338
  sources: [],
338
339
  maxResults: 10,
package/commands/slash.ts CHANGED
@@ -2,6 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
2
2
  import type { HyperspellClient } from "../client.ts"
3
3
  import type { HyperspellConfig } from "../config.ts"
4
4
  import { getWorkspaceDir } from "../config.ts"
5
+ import { resolveUser } from "../lib/sender.ts"
5
6
  import { log } from "../logger.ts"
6
7
  import { syncAllMemoryFiles } from "../sync/markdown.ts"
7
8
 
@@ -18,7 +19,7 @@ function formatScore(score: number | null): string {
18
19
  export function registerCommands(
19
20
  api: OpenClawPluginApi,
20
21
  client: HyperspellClient,
21
- _cfg: HyperspellConfig,
22
+ cfg: HyperspellConfig,
22
23
  ): void {
23
24
  // /getcontext <query> - Search memories and show summaries
24
25
  api.registerCommand({
@@ -26,16 +27,18 @@ export function registerCommands(
26
27
  description: "Search your memories for relevant context",
27
28
  acceptsArgs: true,
28
29
  requireAuth: true,
29
- handler: async (ctx: { args?: string }) => {
30
+ handler: async (ctx: { args?: string; senderId?: string; channel?: string }) => {
30
31
  const query = ctx.args?.trim()
31
32
  if (!query) {
32
33
  return { text: "Usage: /getcontext <search query>" }
33
34
  }
34
35
 
35
- log.debug(`/getcontext command: "${query}"`)
36
+ const resolved = resolveUser(ctx as Record<string, unknown>, cfg)
37
+ const userId = resolved?.userId
38
+ log.debug(`/getcontext command: "${query}" userId=${userId}`)
36
39
 
37
40
  try {
38
- const results = await client.search(query, { limit: 5 })
41
+ const results = await client.search(query, { limit: 5, userId })
39
42
 
40
43
  if (results.length === 0) {
41
44
  return { text: `No memories found for: "${query}"` }
@@ -63,17 +66,20 @@ export function registerCommands(
63
66
  description: "Save something to memory",
64
67
  acceptsArgs: true,
65
68
  requireAuth: true,
66
- handler: async (ctx: { args?: string }) => {
69
+ handler: async (ctx: { args?: string; senderId?: string; channel?: string }) => {
67
70
  const text = ctx.args?.trim()
68
71
  if (!text) {
69
72
  return { text: "Usage: /remember <text to remember>" }
70
73
  }
71
74
 
72
- log.debug(`/remember command: "${truncate(text, 50)}"`)
75
+ const resolved = resolveUser(ctx as Record<string, unknown>, cfg)
76
+ const userId = resolved?.userId
77
+ log.debug(`/remember command: "${truncate(text, 50)}" userId=${userId}`)
73
78
 
74
79
  try {
75
80
  await client.addMemory(text, {
76
81
  metadata: { source: "openclaw_command" },
82
+ userId,
77
83
  })
78
84
 
79
85
  const preview = truncate(text, 60)
@@ -96,7 +102,9 @@ export function registerCommands(
96
102
 
97
103
  try {
98
104
  const workspaceDir = getWorkspaceDir()
99
- const result = await syncAllMemoryFiles(client, workspaceDir)
105
+ const result = await syncAllMemoryFiles(client, workspaceDir, {
106
+ userId: cfg.multiUser?.sharedUserId,
107
+ })
100
108
 
101
109
  if (result.synced === 0 && result.failed === 0) {
102
110
  return { text: "No memory files found in memory/ directory." }
package/config.ts CHANGED
@@ -25,17 +25,32 @@ export type AutoTraceConfig = {
25
25
  metadata?: Record<string, string | number | boolean>;
26
26
  };
27
27
 
28
+ export type UserProfile = {
29
+ userId: string;
30
+ name: string;
31
+ context?: string;
32
+ };
33
+
34
+ export type MultiUserConfig = {
35
+ senderMap: Record<string, UserProfile>;
36
+ sharedUserId: string;
37
+ includeSharedInSearch: boolean;
38
+ };
39
+
28
40
  export type HyperspellConfig = {
29
41
  apiKey: string;
30
42
  userId?: string;
31
43
  autoContext: boolean;
32
44
  autoTrace: AutoTraceConfig;
45
+ emotionalContext: boolean;
46
+ relationshipId?: string;
33
47
  syncMemories: boolean;
34
48
  sources: HyperspellSource[];
35
49
  maxResults: number;
36
50
  relevanceThreshold: number;
37
51
  debug: boolean;
38
52
  knowledgeGraph: KnowledgeGraphConfig;
53
+ multiUser?: MultiUserConfig;
39
54
  };
40
55
 
41
56
  const ALLOWED_KEYS = [
@@ -43,12 +58,15 @@ const ALLOWED_KEYS = [
43
58
  "userId",
44
59
  "autoContext",
45
60
  "autoTrace",
61
+ "emotionalContext",
62
+ "relationshipId",
46
63
  "syncMemories",
47
64
  "sources",
48
65
  "maxResults",
49
66
  "relevanceThreshold",
50
67
  "debug",
51
68
  "knowledgeGraph",
69
+ "multiUser",
52
70
  ];
53
71
 
54
72
  const VALID_SOURCES: HyperspellSource[] = [
@@ -131,6 +149,40 @@ function parseSources(raw: string | string[] | undefined): HyperspellSource[] {
131
149
  return sources;
132
150
  }
133
151
 
152
+ function parseMultiUser(raw: unknown): MultiUserConfig | undefined {
153
+ if (!raw || typeof raw !== "object") return undefined;
154
+ const mu = raw as Record<string, unknown>;
155
+
156
+ const senderMap: Record<string, UserProfile> = {};
157
+ const rawMap = mu.senderMap as Record<string, unknown> | undefined;
158
+ if (rawMap && typeof rawMap === "object") {
159
+ for (const [handle, profile] of Object.entries(rawMap)) {
160
+ if (profile && typeof profile === "object") {
161
+ const p = profile as Record<string, unknown>;
162
+ if (typeof p.userId === "string" && typeof p.name === "string") {
163
+ senderMap[handle] = {
164
+ userId: p.userId,
165
+ name: p.name,
166
+ context: typeof p.context === "string" ? p.context : undefined,
167
+ };
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ if (Object.keys(senderMap).length === 0) return undefined;
174
+
175
+ return {
176
+ senderMap,
177
+ sharedUserId:
178
+ typeof mu.sharedUserId === "string" ? mu.sharedUserId : "shared",
179
+ includeSharedInSearch:
180
+ typeof mu.includeSharedInSearch === "boolean"
181
+ ? mu.includeSharedInSearch
182
+ : true,
183
+ };
184
+ }
185
+
134
186
  export function parseConfig(raw: unknown): HyperspellConfig {
135
187
  const cfg =
136
188
  raw && typeof raw === "object" && !Array.isArray(raw)
@@ -168,6 +220,8 @@ export function parseConfig(raw: unknown): HyperspellConfig {
168
220
  | Record<string, string | number | boolean>
169
221
  | undefined,
170
222
  },
223
+ emotionalContext: (cfg.emotionalContext as boolean) ?? false,
224
+ relationshipId: cfg.relationshipId as string | undefined,
171
225
  syncMemories: (cfg.syncMemories as boolean) ?? false,
172
226
  sources: parseSources(cfg.sources as string | string[] | undefined),
173
227
  maxResults: (cfg.maxResults as number) ?? 10,
@@ -178,6 +232,7 @@ export function parseConfig(raw: unknown): HyperspellConfig {
178
232
  scanIntervalMinutes: (kgRaw.scanIntervalMinutes as number) ?? 60,
179
233
  batchSize: (kgRaw.batchSize as number) ?? 20,
180
234
  },
235
+ multiUser: parseMultiUser(cfg.multiUser),
181
236
  };
182
237
  }
183
238