@hyperspell/openclaw-hyperspell 0.9.0 → 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 +139 -59
- package/commands/slash.ts +15 -7
- package/config.ts +49 -0
- package/graph/ops.ts +34 -28
- package/graph/tools.ts +1 -1
- package/hooks/auto-context.ts +163 -10
- package/hooks/auto-trace.ts +12 -2
- package/hooks/memory-sync.ts +5 -3
- package/index.ts +12 -6
- package/lib/sender.ts +76 -0
- package/package.json +1 -1
- package/sync/markdown.ts +4 -1
- package/tools/remember.ts +56 -48
- package/tools/search.ts +86 -82
- package/types/openclaw.d.ts +16 -0
package/client.ts
CHANGED
|
@@ -82,29 +82,52 @@ export class HyperspellClient {
|
|
|
82
82
|
return headers;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
private requestOptions(userId?: string) {
|
|
86
|
+
if (!userId) return undefined;
|
|
87
|
+
return { headers: { "X-As-User": userId } };
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
async search(
|
|
86
91
|
query: string,
|
|
87
|
-
options?: {
|
|
92
|
+
options?: {
|
|
93
|
+
limit?: number;
|
|
94
|
+
sources?: HyperspellSource[];
|
|
95
|
+
after?: string;
|
|
96
|
+
before?: string;
|
|
97
|
+
userId?: string;
|
|
98
|
+
},
|
|
88
99
|
): Promise<SearchResult[]> {
|
|
89
100
|
const limit = options?.limit ?? this.config.maxResults;
|
|
90
101
|
const sources =
|
|
91
102
|
options?.sources ??
|
|
92
103
|
(this.config.sources.length > 0 ? this.config.sources : undefined);
|
|
93
104
|
|
|
94
|
-
log.debugRequest("memories.search", {
|
|
95
|
-
|
|
96
|
-
const response = await this.client.memories.search({
|
|
105
|
+
log.debugRequest("memories.search", {
|
|
97
106
|
query,
|
|
107
|
+
limit,
|
|
98
108
|
sources,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...(options?.before ? { before: options.before } : {}),
|
|
103
|
-
},
|
|
109
|
+
after: options?.after,
|
|
110
|
+
before: options?.before,
|
|
111
|
+
userId: options?.userId,
|
|
104
112
|
});
|
|
105
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
|
+
|
|
106
127
|
const results: SearchResult[] = response.documents.map((doc) => {
|
|
107
|
-
const raw = doc as typeof doc & {
|
|
128
|
+
const raw = doc as typeof doc & {
|
|
129
|
+
highlights?: Array<{ id: string; score: number; text: string }>;
|
|
130
|
+
};
|
|
108
131
|
return {
|
|
109
132
|
resourceId: doc.resource_id,
|
|
110
133
|
title: doc.title ?? null,
|
|
@@ -112,7 +135,11 @@ export class HyperspellClient {
|
|
|
112
135
|
score: doc.score ?? null,
|
|
113
136
|
url: (doc.metadata?.url as string | null) ?? null,
|
|
114
137
|
createdAt: (doc.metadata?.created_at as string | null) ?? null,
|
|
115
|
-
highlights: (raw.highlights ?? []).map((h) => ({
|
|
138
|
+
highlights: (raw.highlights ?? []).map((h) => ({
|
|
139
|
+
id: h.id,
|
|
140
|
+
score: h.score,
|
|
141
|
+
text: h.text,
|
|
142
|
+
})),
|
|
116
143
|
};
|
|
117
144
|
});
|
|
118
145
|
|
|
@@ -122,25 +149,41 @@ export class HyperspellClient {
|
|
|
122
149
|
|
|
123
150
|
async searchRaw(
|
|
124
151
|
query: string,
|
|
125
|
-
options?: {
|
|
152
|
+
options?: {
|
|
153
|
+
limit?: number;
|
|
154
|
+
sources?: HyperspellSource[];
|
|
155
|
+
after?: string;
|
|
156
|
+
before?: string;
|
|
157
|
+
userId?: string;
|
|
158
|
+
},
|
|
126
159
|
): Promise<Record<string, unknown>> {
|
|
127
160
|
const limit = options?.limit ?? this.config.maxResults;
|
|
128
161
|
const sources =
|
|
129
162
|
options?.sources ??
|
|
130
163
|
(this.config.sources.length > 0 ? this.config.sources : undefined);
|
|
131
164
|
|
|
132
|
-
log.debugRequest("memories.search (raw)", {
|
|
133
|
-
|
|
134
|
-
const response = await this.client.memories.search({
|
|
165
|
+
log.debugRequest("memories.search (raw)", {
|
|
135
166
|
query,
|
|
167
|
+
limit,
|
|
136
168
|
sources,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
...(options?.before ? { before: options.before } : {}),
|
|
141
|
-
},
|
|
169
|
+
after: options?.after,
|
|
170
|
+
before: options?.before,
|
|
171
|
+
userId: options?.userId,
|
|
142
172
|
});
|
|
143
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
|
+
|
|
144
187
|
log.debugResponse("memories.search (raw)", {
|
|
145
188
|
count: response.documents.length,
|
|
146
189
|
});
|
|
@@ -150,7 +193,11 @@ export class HyperspellClient {
|
|
|
150
193
|
|
|
151
194
|
async searchWithAnswer(
|
|
152
195
|
query: string,
|
|
153
|
-
options?: {
|
|
196
|
+
options?: {
|
|
197
|
+
limit?: number;
|
|
198
|
+
sources?: HyperspellSource[];
|
|
199
|
+
userId?: string;
|
|
200
|
+
},
|
|
154
201
|
): Promise<SearchWithAnswerResult> {
|
|
155
202
|
const limit = options?.limit ?? this.config.maxResults;
|
|
156
203
|
const sources =
|
|
@@ -161,16 +208,20 @@ export class HyperspellClient {
|
|
|
161
208
|
query,
|
|
162
209
|
limit,
|
|
163
210
|
sources,
|
|
211
|
+
userId: options?.userId,
|
|
164
212
|
});
|
|
165
213
|
|
|
166
|
-
const response = await this.client.memories.search(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
214
|
+
const response = await this.client.memories.search(
|
|
215
|
+
{
|
|
216
|
+
query,
|
|
217
|
+
sources,
|
|
218
|
+
answer: true,
|
|
219
|
+
options: {
|
|
220
|
+
max_results: limit,
|
|
221
|
+
},
|
|
172
222
|
},
|
|
173
|
-
|
|
223
|
+
this.requestOptions(options?.userId),
|
|
224
|
+
);
|
|
174
225
|
|
|
175
226
|
const documents: SearchResult[] = response.documents.map((doc) => ({
|
|
176
227
|
resourceId: doc.resource_id,
|
|
@@ -201,6 +252,7 @@ export class HyperspellClient {
|
|
|
201
252
|
collection?: string;
|
|
202
253
|
date?: string;
|
|
203
254
|
metadata?: Record<string, string | number | boolean>;
|
|
255
|
+
userId?: string;
|
|
204
256
|
},
|
|
205
257
|
): Promise<{ resourceId: string }> {
|
|
206
258
|
log.debugRequest("memories.add", {
|
|
@@ -209,19 +261,23 @@ export class HyperspellClient {
|
|
|
209
261
|
resourceId: options?.resourceId,
|
|
210
262
|
collection: options?.collection,
|
|
211
263
|
date: options?.date,
|
|
264
|
+
userId: options?.userId,
|
|
212
265
|
});
|
|
213
266
|
|
|
214
|
-
const result = await this.client.memories.add(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
},
|
|
223
278
|
},
|
|
224
|
-
|
|
279
|
+
this.requestOptions(options?.userId),
|
|
280
|
+
);
|
|
225
281
|
|
|
226
282
|
log.debugResponse("memories.add", { resourceId: result.resource_id });
|
|
227
283
|
return { resourceId: result.resource_id };
|
|
@@ -245,10 +301,15 @@ export class HyperspellClient {
|
|
|
245
301
|
|
|
246
302
|
async getConnectUrl(
|
|
247
303
|
integrationId: string,
|
|
304
|
+
options?: { userId?: string },
|
|
248
305
|
): Promise<{ url: string; expiresAt: string }> {
|
|
249
306
|
log.debugRequest("integrations.connect", { integrationId });
|
|
250
307
|
|
|
251
|
-
const response = await this.client.integrations.connect(
|
|
308
|
+
const response = await this.client.integrations.connect(
|
|
309
|
+
integrationId,
|
|
310
|
+
undefined,
|
|
311
|
+
this.requestOptions(options?.userId),
|
|
312
|
+
);
|
|
252
313
|
|
|
253
314
|
log.debugResponse("integrations.connect", { url: response.url });
|
|
254
315
|
return {
|
|
@@ -261,6 +322,7 @@ export class HyperspellClient {
|
|
|
261
322
|
source?: HyperspellSource;
|
|
262
323
|
collection?: string;
|
|
263
324
|
pageSize?: number;
|
|
325
|
+
userId?: string;
|
|
264
326
|
}): AsyncGenerator<{
|
|
265
327
|
resourceId: string;
|
|
266
328
|
source: HyperspellSource;
|
|
@@ -270,6 +332,7 @@ export class HyperspellClient {
|
|
|
270
332
|
log.debugRequest("memories.list", {
|
|
271
333
|
source: options?.source,
|
|
272
334
|
collection: options?.collection,
|
|
335
|
+
userId: options?.userId,
|
|
273
336
|
});
|
|
274
337
|
|
|
275
338
|
const params: Record<string, unknown> = {
|
|
@@ -278,7 +341,10 @@ export class HyperspellClient {
|
|
|
278
341
|
if (options?.source) params.source = options.source;
|
|
279
342
|
if (options?.collection) params.collection = options.collection;
|
|
280
343
|
|
|
281
|
-
for await (const memory of this.client.memories.list(
|
|
344
|
+
for await (const memory of this.client.memories.list(
|
|
345
|
+
params as any,
|
|
346
|
+
this.requestOptions(options?.userId),
|
|
347
|
+
)) {
|
|
282
348
|
yield {
|
|
283
349
|
resourceId: memory.resource_id,
|
|
284
350
|
source: memory.source as HyperspellSource,
|
|
@@ -291,10 +357,15 @@ export class HyperspellClient {
|
|
|
291
357
|
async getMemory(
|
|
292
358
|
resourceId: string,
|
|
293
359
|
source: HyperspellSource,
|
|
360
|
+
options?: { userId?: string },
|
|
294
361
|
): Promise<Record<string, unknown>> {
|
|
295
362
|
log.debugRequest("memories.get", { resourceId, source });
|
|
296
363
|
|
|
297
|
-
const response = await this.client.memories.get(
|
|
364
|
+
const response = await this.client.memories.get(
|
|
365
|
+
resourceId,
|
|
366
|
+
{ source },
|
|
367
|
+
this.requestOptions(options?.userId),
|
|
368
|
+
);
|
|
298
369
|
const raw = response as unknown as Record<string, unknown>;
|
|
299
370
|
|
|
300
371
|
log.debugResponse("memories.get", { resourceId, hasData: "data" in raw });
|
|
@@ -308,30 +379,35 @@ export class HyperspellClient {
|
|
|
308
379
|
title?: string;
|
|
309
380
|
extract?: Array<"procedure" | "memory" | "mood">;
|
|
310
381
|
metadata?: Record<string, string | number | boolean>;
|
|
382
|
+
userId?: string;
|
|
311
383
|
},
|
|
312
384
|
): Promise<{ resourceId: string; status: string }> {
|
|
313
385
|
log.debugRequest("sessions.add", {
|
|
314
386
|
historyLength: history.length,
|
|
315
387
|
sessionId: options?.sessionId,
|
|
316
388
|
extract: options?.extract,
|
|
389
|
+
userId: options?.userId,
|
|
317
390
|
});
|
|
318
391
|
|
|
319
|
-
const result = await this.client.sessions.add(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"procedure"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
},
|
|
333
408
|
},
|
|
334
|
-
|
|
409
|
+
this.requestOptions(options?.userId),
|
|
410
|
+
);
|
|
335
411
|
|
|
336
412
|
log.debugResponse("sessions.add", {
|
|
337
413
|
resourceId: result.resource_id,
|
|
@@ -340,10 +416,14 @@ export class HyperspellClient {
|
|
|
340
416
|
return { resourceId: result.resource_id, status: result.status };
|
|
341
417
|
}
|
|
342
418
|
|
|
343
|
-
async listConnections(
|
|
344
|
-
|
|
419
|
+
async listConnections(options?: {
|
|
420
|
+
userId?: string;
|
|
421
|
+
}): Promise<Connection[]> {
|
|
422
|
+
log.debugRequest("connections.list", { userId: options?.userId });
|
|
345
423
|
|
|
346
|
-
const response = await this.client.connections.list(
|
|
424
|
+
const response = await this.client.connections.list(
|
|
425
|
+
this.requestOptions(options?.userId),
|
|
426
|
+
);
|
|
347
427
|
|
|
348
428
|
const connections: Connection[] = response.connections.map((conn) => ({
|
|
349
429
|
id: conn.id,
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,6 +25,18 @@ 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;
|
|
@@ -38,6 +50,7 @@ export type HyperspellConfig = {
|
|
|
38
50
|
relevanceThreshold: number;
|
|
39
51
|
debug: boolean;
|
|
40
52
|
knowledgeGraph: KnowledgeGraphConfig;
|
|
53
|
+
multiUser?: MultiUserConfig;
|
|
41
54
|
};
|
|
42
55
|
|
|
43
56
|
const ALLOWED_KEYS = [
|
|
@@ -53,6 +66,7 @@ const ALLOWED_KEYS = [
|
|
|
53
66
|
"relevanceThreshold",
|
|
54
67
|
"debug",
|
|
55
68
|
"knowledgeGraph",
|
|
69
|
+
"multiUser",
|
|
56
70
|
];
|
|
57
71
|
|
|
58
72
|
const VALID_SOURCES: HyperspellSource[] = [
|
|
@@ -135,6 +149,40 @@ function parseSources(raw: string | string[] | undefined): HyperspellSource[] {
|
|
|
135
149
|
return sources;
|
|
136
150
|
}
|
|
137
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
|
+
|
|
138
186
|
export function parseConfig(raw: unknown): HyperspellConfig {
|
|
139
187
|
const cfg =
|
|
140
188
|
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
@@ -184,6 +232,7 @@ export function parseConfig(raw: unknown): HyperspellConfig {
|
|
|
184
232
|
scanIntervalMinutes: (kgRaw.scanIntervalMinutes as number) ?? 60,
|
|
185
233
|
batchSize: (kgRaw.batchSize as number) ?? 20,
|
|
186
234
|
},
|
|
235
|
+
multiUser: parseMultiUser(cfg.multiUser),
|
|
187
236
|
};
|
|
188
237
|
}
|
|
189
238
|
|
package/graph/ops.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs"
|
|
2
2
|
import * as path from "node:path"
|
|
3
3
|
import type { HyperspellClient } from "../client.ts"
|
|
4
|
+
import type { HyperspellConfig } from "../config.ts"
|
|
5
|
+
import { getAllUserIds } from "../lib/sender.ts"
|
|
4
6
|
import { log } from "../logger.ts"
|
|
5
7
|
import { NetworkStateManager } from "./state.ts"
|
|
6
8
|
|
|
@@ -93,38 +95,42 @@ export async function scanMemories(
|
|
|
93
95
|
client: HyperspellClient,
|
|
94
96
|
stateManager: NetworkStateManager,
|
|
95
97
|
batchSize: number,
|
|
98
|
+
cfg?: HyperspellConfig,
|
|
96
99
|
): Promise<ScannedMemory[]> {
|
|
97
100
|
const unprocessed: ScannedMemory[] = []
|
|
101
|
+
const userIds = cfg ? getAllUserIds(cfg) : [undefined]
|
|
102
|
+
|
|
103
|
+
outer: for (const userId of userIds) {
|
|
104
|
+
for await (const mem of client.listMemories({ userId })) {
|
|
105
|
+
if (stateManager.isProcessed(mem.resourceId)) continue
|
|
106
|
+
if (mem.metadata?.graph_entity === "true" || mem.metadata?.graph_entity === true) continue
|
|
107
|
+
if ((mem.metadata?.status as string) !== "completed") continue
|
|
108
|
+
|
|
109
|
+
let summary = ""
|
|
110
|
+
try {
|
|
111
|
+
const full = await client.getMemory(mem.resourceId, mem.source as import("../config.ts").HyperspellSource, { userId })
|
|
112
|
+
const data = full.data as unknown[] | undefined
|
|
113
|
+
|
|
114
|
+
const participants = full.participants as Array<{ name?: string; email?: string }> | undefined
|
|
115
|
+
const participantLine = participants?.length
|
|
116
|
+
? `Participants: ${participants.map((p) => `${p.name || ""} <${p.email || ""}>`).join(", ")}`
|
|
117
|
+
: ""
|
|
118
|
+
|
|
119
|
+
const dataSummary = data ? summarizeMemoryData(data, mem.source) : ""
|
|
120
|
+
summary = [participantLine, dataSummary].filter(Boolean).join("\n\n")
|
|
121
|
+
} catch {
|
|
122
|
+
summary = "(content unavailable)"
|
|
123
|
+
}
|
|
98
124
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const full = await client.getMemory(mem.resourceId, mem.source)
|
|
107
|
-
const data = full.data as unknown[] | undefined
|
|
108
|
-
|
|
109
|
-
const participants = full.participants as Array<{ name?: string; email?: string }> | undefined
|
|
110
|
-
const participantLine = participants?.length
|
|
111
|
-
? `Participants: ${participants.map((p) => `${p.name || ""} <${p.email || ""}>`).join(", ")}`
|
|
112
|
-
: ""
|
|
113
|
-
|
|
114
|
-
const dataSummary = data ? summarizeMemoryData(data, mem.source) : ""
|
|
115
|
-
summary = [participantLine, dataSummary].filter(Boolean).join("\n\n")
|
|
116
|
-
} catch {
|
|
117
|
-
summary = "(content unavailable)"
|
|
118
|
-
}
|
|
125
|
+
unprocessed.push({
|
|
126
|
+
resourceId: mem.resourceId,
|
|
127
|
+
source: mem.source,
|
|
128
|
+
title: mem.title,
|
|
129
|
+
summary: summary.slice(0, 1000),
|
|
130
|
+
})
|
|
119
131
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
source: mem.source,
|
|
123
|
-
title: mem.title,
|
|
124
|
-
summary: summary.slice(0, 1000),
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
if (unprocessed.length >= batchSize) break
|
|
132
|
+
if (unprocessed.length >= batchSize) break outer
|
|
133
|
+
}
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
return unprocessed
|
package/graph/tools.ts
CHANGED
|
@@ -32,7 +32,7 @@ export function registerNetworkTools(
|
|
|
32
32
|
async execute(_toolCallId: string, params: { batchSize?: number }) {
|
|
33
33
|
const batchSize = params.batchSize ?? cfg.knowledgeGraph.batchSize
|
|
34
34
|
try {
|
|
35
|
-
const memories = await scanMemories(client, stateManager, batchSize)
|
|
35
|
+
const memories = await scanMemories(client, stateManager, batchSize, cfg)
|
|
36
36
|
const text = formatScanResults(memories, stateManager.getProcessedCount(), stateManager.getLastScanAt())
|
|
37
37
|
return {
|
|
38
38
|
content: [{ type: "text" as const, text }],
|