@hyperspell/openclaw-hyperspell 0.12.0 → 0.13.1

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 (50) hide show
  1. package/README.md +37 -1
  2. package/dist/client.js +341 -0
  3. package/dist/commands/setup.js +568 -0
  4. package/dist/commands/slash.js +159 -0
  5. package/dist/config.js +349 -0
  6. package/{graph/cron.ts → dist/graph/cron.js} +11 -15
  7. package/dist/graph/index.js +4 -0
  8. package/dist/graph/ops.js +221 -0
  9. package/dist/graph/state.js +68 -0
  10. package/dist/graph/tools.js +87 -0
  11. package/dist/hooks/auto-context.js +199 -0
  12. package/dist/hooks/auto-trace.js +143 -0
  13. package/dist/hooks/emotional-state.js +155 -0
  14. package/dist/hooks/memory-sync.js +54 -0
  15. package/dist/hooks/startup-orientation.js +236 -0
  16. package/dist/index.js +132 -0
  17. package/dist/lib/browser.js +29 -0
  18. package/dist/lib/sender.js +173 -0
  19. package/dist/lib/voice-id.js +22 -0
  20. package/dist/logger.js +32 -0
  21. package/dist/sync/markdown.js +151 -0
  22. package/dist/tools/remember.js +97 -0
  23. package/dist/tools/search.js +87 -0
  24. package/openclaw.plugin.json +25 -1
  25. package/package.json +7 -12
  26. package/client.ts +0 -566
  27. package/commands/setup.ts +0 -673
  28. package/commands/slash.ts +0 -198
  29. package/config.test.ts +0 -202
  30. package/config.ts +0 -497
  31. package/graph/index.ts +0 -5
  32. package/graph/ops.ts +0 -259
  33. package/graph/state.ts +0 -79
  34. package/graph/tools.ts +0 -117
  35. package/hooks/auto-context.ts +0 -272
  36. package/hooks/auto-trace.test.ts +0 -81
  37. package/hooks/auto-trace.ts +0 -197
  38. package/hooks/emotional-state.test.ts +0 -160
  39. package/hooks/emotional-state.ts +0 -179
  40. package/hooks/memory-sync.ts +0 -65
  41. package/index.ts +0 -152
  42. package/lib/browser.ts +0 -31
  43. package/lib/sender.test.ts +0 -234
  44. package/lib/sender.ts +0 -234
  45. package/lib/voice-id.ts +0 -39
  46. package/logger.ts +0 -41
  47. package/sync/markdown.ts +0 -186
  48. package/tools/remember.ts +0 -132
  49. package/tools/search.ts +0 -131
  50. package/types/openclaw.d.ts +0 -76
package/config.ts DELETED
@@ -1,497 +0,0 @@
1
- export type HyperspellSource =
2
- | "reddit"
3
- | "notion"
4
- | "slack"
5
- | "google_calendar"
6
- | "google_mail"
7
- | "box"
8
- | "google_drive"
9
- | "vault"
10
- | "web_crawler"
11
- | "dropbox"
12
- | "github"
13
- | "trace"
14
- | "microsoft_teams";
15
-
16
- export type KnowledgeGraphConfig = {
17
- enabled: boolean;
18
- scanIntervalMinutes: number;
19
- batchSize: number;
20
- };
21
-
22
- export type AutoTraceConfig = {
23
- enabled: boolean;
24
- extract: Array<"procedure" | "memory" | "mood">;
25
- metadata?: Record<string, string | number | boolean>;
26
- };
27
-
28
- export type UserProfile = {
29
- userId: string;
30
- name: string;
31
- context?: string;
32
- role?: string;
33
- };
34
-
35
- export type ScopeName = string;
36
- export type CanReadScope = ScopeName | "*" | "self";
37
-
38
- export type Role = {
39
- canRead: CanReadScope[];
40
- defaultWriteScope: ScopeName;
41
- canWriteScopes?: ScopeName[];
42
- };
43
-
44
- export type VoiceIdConfig = {
45
- enabled: boolean;
46
- adapter?: string;
47
- confidenceThreshold?: number;
48
- };
49
-
50
- export type ScopingConfig = {
51
- enabled: boolean;
52
- defaultScope: ScopeName;
53
- scopes: ScopeName[];
54
- roles: Record<string, Role>;
55
- users: Record<string, { role: string }>;
56
- collections?: Record<ScopeName, string>;
57
- voiceId?: VoiceIdConfig;
58
- };
59
-
60
- export type MultiUserConfig = {
61
- senderMap: Record<string, UserProfile>;
62
- sharedUserId: string;
63
- includeSharedInSearch: boolean;
64
- scoping?: ScopingConfig;
65
- };
66
-
67
- /**
68
- * Convert user-facing scope names (which may contain hyphens) to SDK-safe
69
- * metadata values (alphanumeric + underscore only). Must be applied at every
70
- * boundary where scopes cross into Hyperspell metadata — writes and reads —
71
- * or filters will silently miss.
72
- */
73
- export function normalizeScope(scope: ScopeName): string {
74
- return scope.replace(/[^a-zA-Z0-9_]/g, "_");
75
- }
76
-
77
- export type HyperspellConfig = {
78
- apiKey: string;
79
- userId?: string;
80
- autoContext: boolean;
81
- autoTrace: AutoTraceConfig;
82
- emotionalContext: boolean;
83
- relationshipId?: string;
84
- syncMemories: boolean;
85
- sources: HyperspellSource[];
86
- maxResults: number;
87
- relevanceThreshold: number;
88
- debug: boolean;
89
- knowledgeGraph: KnowledgeGraphConfig;
90
- multiUser?: MultiUserConfig;
91
- };
92
-
93
- const ALLOWED_KEYS = [
94
- "apiKey",
95
- "userId",
96
- "autoContext",
97
- "autoTrace",
98
- "emotionalContext",
99
- "relationshipId",
100
- "syncMemories",
101
- "sources",
102
- "maxResults",
103
- "relevanceThreshold",
104
- "debug",
105
- "knowledgeGraph",
106
- "multiUser",
107
- ];
108
-
109
- const VALID_SOURCES: HyperspellSource[] = [
110
- "reddit",
111
- "notion",
112
- "slack",
113
- "google_calendar",
114
- "google_mail",
115
- "box",
116
- "google_drive",
117
- "vault",
118
- "web_crawler",
119
- "dropbox",
120
- "github",
121
- "trace",
122
- "microsoft_teams",
123
- ];
124
-
125
- function assertAllowedKeys(
126
- value: Record<string, unknown>,
127
- allowed: string[],
128
- label: string,
129
- ): void {
130
- const unknown = Object.keys(value).filter((k) => !allowed.includes(k));
131
- if (unknown.length > 0) {
132
- throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
133
- }
134
- }
135
-
136
- function resolveEnvVars(value: string): string {
137
- return value.replace(/\$\{([^}]+)\}/g, (_, envVar: string) => {
138
- const envValue = process.env[envVar];
139
- if (!envValue) {
140
- throw new Error(`Environment variable ${envVar} is not set`);
141
- }
142
- return envValue;
143
- });
144
- }
145
-
146
- function parseSources(raw: string | string[] | undefined): HyperspellSource[] {
147
- if (!raw) {
148
- return [];
149
- }
150
-
151
- // Handle array input
152
- if (Array.isArray(raw)) {
153
- const sources = raw
154
- .map((s) => String(s).trim().toLowerCase())
155
- .filter((s) => s.length > 0) as HyperspellSource[];
156
-
157
- for (const source of sources) {
158
- if (!VALID_SOURCES.includes(source)) {
159
- throw new Error(
160
- `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
161
- );
162
- }
163
- }
164
-
165
- return sources;
166
- }
167
-
168
- // Handle string input (comma-separated)
169
- if (typeof raw === "string" && raw.trim() === "") {
170
- return [];
171
- }
172
-
173
- const sources = raw
174
- .split(",")
175
- .map((s) => s.trim().toLowerCase())
176
- .filter((s) => s.length > 0) as HyperspellSource[];
177
-
178
- for (const source of sources) {
179
- if (!VALID_SOURCES.includes(source)) {
180
- throw new Error(
181
- `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
182
- );
183
- }
184
- }
185
-
186
- return sources;
187
- }
188
-
189
- function parseScoping(raw: unknown): ScopingConfig | undefined {
190
- if (!raw || typeof raw !== "object") return undefined;
191
- const sc = raw as Record<string, unknown>;
192
-
193
- if (sc.enabled !== true) return undefined;
194
-
195
- const scopes = Array.isArray(sc.scopes)
196
- ? (sc.scopes.filter((s) => typeof s === "string") as ScopeName[])
197
- : [];
198
- if (scopes.length === 0) {
199
- throw new Error("scoping.scopes must be a non-empty array of scope names");
200
- }
201
-
202
- const defaultScope =
203
- typeof sc.defaultScope === "string" ? sc.defaultScope : "private";
204
- if (!scopes.includes(defaultScope)) {
205
- throw new Error(
206
- `scoping.defaultScope "${defaultScope}" must be one of scopes: ${scopes.join(", ")}`,
207
- );
208
- }
209
-
210
- const rolesRaw =
211
- sc.roles && typeof sc.roles === "object"
212
- ? (sc.roles as Record<string, unknown>)
213
- : {};
214
- const roles: Record<string, Role> = {};
215
- for (const [roleName, rRaw] of Object.entries(rolesRaw)) {
216
- if (!rRaw || typeof rRaw !== "object") continue;
217
- const r = rRaw as Record<string, unknown>;
218
-
219
- const canRead = Array.isArray(r.canRead)
220
- ? (r.canRead.filter((s) => typeof s === "string") as CanReadScope[])
221
- : [];
222
- for (const s of canRead) {
223
- if (s === "*" || s === "self") continue;
224
- if (!scopes.includes(s)) {
225
- throw new Error(
226
- `scoping.roles.${roleName}.canRead contains unknown scope "${s}"`,
227
- );
228
- }
229
- }
230
-
231
- const defaultWriteScope =
232
- typeof r.defaultWriteScope === "string"
233
- ? r.defaultWriteScope
234
- : defaultScope;
235
- if (!scopes.includes(defaultWriteScope)) {
236
- throw new Error(
237
- `scoping.roles.${roleName}.defaultWriteScope "${defaultWriteScope}" must be one of scopes: ${scopes.join(", ")}`,
238
- );
239
- }
240
-
241
- let canWriteScopes: ScopeName[] | undefined;
242
- if (Array.isArray(r.canWriteScopes)) {
243
- canWriteScopes = r.canWriteScopes.filter(
244
- (s): s is ScopeName => typeof s === "string",
245
- );
246
- for (const s of canWriteScopes) {
247
- if (!scopes.includes(s)) {
248
- throw new Error(
249
- `scoping.roles.${roleName}.canWriteScopes contains unknown scope "${s}"`,
250
- );
251
- }
252
- }
253
- }
254
-
255
- roles[roleName] = { canRead, defaultWriteScope, canWriteScopes };
256
- }
257
-
258
- const usersRaw =
259
- sc.users && typeof sc.users === "object"
260
- ? (sc.users as Record<string, unknown>)
261
- : {};
262
- const users: Record<string, { role: string }> = {};
263
- for (const [uid, uRaw] of Object.entries(usersRaw)) {
264
- if (!uRaw || typeof uRaw !== "object") continue;
265
- const u = uRaw as Record<string, unknown>;
266
- if (typeof u.role !== "string") continue;
267
- if (!roles[u.role]) {
268
- throw new Error(
269
- `scoping.users.${uid}.role "${u.role}" does not key into scoping.roles`,
270
- );
271
- }
272
- users[uid] = { role: u.role };
273
- }
274
-
275
- const collectionsRaw =
276
- sc.collections && typeof sc.collections === "object"
277
- ? (sc.collections as Record<string, unknown>)
278
- : undefined;
279
- let collections: Record<ScopeName, string> | undefined;
280
- if (collectionsRaw) {
281
- collections = {};
282
- for (const [scopeName, coll] of Object.entries(collectionsRaw)) {
283
- if (typeof coll === "string" && scopes.includes(scopeName)) {
284
- collections[scopeName] = coll;
285
- }
286
- }
287
- }
288
-
289
- let voiceId: VoiceIdConfig | undefined;
290
- if (sc.voiceId && typeof sc.voiceId === "object") {
291
- const v = sc.voiceId as Record<string, unknown>;
292
- voiceId = {
293
- enabled: v.enabled === true,
294
- adapter: typeof v.adapter === "string" ? v.adapter : undefined,
295
- confidenceThreshold:
296
- typeof v.confidenceThreshold === "number"
297
- ? v.confidenceThreshold
298
- : undefined,
299
- };
300
- }
301
-
302
- return {
303
- enabled: true,
304
- defaultScope,
305
- scopes,
306
- roles,
307
- users,
308
- collections,
309
- voiceId,
310
- };
311
- }
312
-
313
- function parseMultiUser(raw: unknown): MultiUserConfig | undefined {
314
- if (!raw || typeof raw !== "object") return undefined;
315
- const mu = raw as Record<string, unknown>;
316
-
317
- const senderMap: Record<string, UserProfile> = {};
318
- const rawMap = mu.senderMap as Record<string, unknown> | undefined;
319
- if (rawMap && typeof rawMap === "object") {
320
- for (const [handle, profile] of Object.entries(rawMap)) {
321
- if (profile && typeof profile === "object") {
322
- const p = profile as Record<string, unknown>;
323
- if (typeof p.userId === "string" && typeof p.name === "string") {
324
- senderMap[handle] = {
325
- userId: p.userId,
326
- name: p.name,
327
- context: typeof p.context === "string" ? p.context : undefined,
328
- role: typeof p.role === "string" ? p.role : undefined,
329
- };
330
- }
331
- }
332
- }
333
- }
334
-
335
- if (Object.keys(senderMap).length === 0) return undefined;
336
-
337
- return {
338
- senderMap,
339
- sharedUserId:
340
- typeof mu.sharedUserId === "string" ? mu.sharedUserId : "shared",
341
- includeSharedInSearch:
342
- typeof mu.includeSharedInSearch === "boolean"
343
- ? mu.includeSharedInSearch
344
- : true,
345
- scoping: parseScoping(mu.scoping),
346
- };
347
- }
348
-
349
- export function parseConfig(raw: unknown): HyperspellConfig {
350
- const cfg =
351
- raw && typeof raw === "object" && !Array.isArray(raw)
352
- ? (raw as Record<string, unknown>)
353
- : {};
354
-
355
- if (Object.keys(cfg).length > 0) {
356
- assertAllowedKeys(cfg, ALLOWED_KEYS, "hyperspell config");
357
- }
358
-
359
- const apiKey =
360
- typeof cfg.apiKey === "string" && cfg.apiKey.length > 0
361
- ? resolveEnvVars(cfg.apiKey)
362
- : process.env.HYPERSPELL_API_KEY;
363
-
364
- if (!apiKey) {
365
- throw new Error(
366
- "hyperspell: apiKey is required (set in plugin config or HYPERSPELL_API_KEY env var)",
367
- );
368
- }
369
-
370
- const kgRaw = (cfg.knowledgeGraph ?? {}) as Record<string, unknown>;
371
- const atRaw = (cfg.autoTrace ?? {}) as Record<string, unknown>;
372
-
373
- return {
374
- apiKey,
375
- userId: cfg.userId as string | undefined,
376
- autoContext: (cfg.autoContext as boolean) ?? true,
377
- autoTrace: {
378
- enabled: (atRaw.enabled as boolean) ?? false,
379
- extract: (atRaw.extract as Array<"procedure" | "memory" | "mood">) ?? [
380
- "procedure",
381
- ],
382
- metadata: atRaw.metadata as
383
- | Record<string, string | number | boolean>
384
- | undefined,
385
- },
386
- emotionalContext: (cfg.emotionalContext as boolean) ?? false,
387
- relationshipId: cfg.relationshipId as string | undefined,
388
- syncMemories: (cfg.syncMemories as boolean) ?? false,
389
- sources: parseSources(cfg.sources as string | string[] | undefined),
390
- maxResults: (cfg.maxResults as number) ?? 10,
391
- relevanceThreshold: (cfg.relevanceThreshold as number) ?? 0.6,
392
- debug: (cfg.debug as boolean) ?? false,
393
- knowledgeGraph: {
394
- enabled: (kgRaw.enabled as boolean) ?? false,
395
- scanIntervalMinutes: (kgRaw.scanIntervalMinutes as number) ?? 60,
396
- batchSize: (kgRaw.batchSize as number) ?? 20,
397
- },
398
- multiUser: parseMultiUser(cfg.multiUser),
399
- };
400
- }
401
-
402
- export const hyperspellConfigSchema = {
403
- parse: parseConfig,
404
- };
405
-
406
- /**
407
- * Resolve OpenClaw state directory (matches OpenClaw's own logic).
408
- */
409
- export function resolveStateDir(): string {
410
- const { homedir } = require("node:os");
411
- const path = require("node:path");
412
-
413
- const override =
414
- process.env.OPENCLAW_STATE_DIR?.trim() ||
415
- process.env.CLAWDBOT_STATE_DIR?.trim();
416
- if (override) {
417
- return override.startsWith("~")
418
- ? override.replace(/^~(?=$|[\\/])/, homedir())
419
- : path.resolve(override);
420
- }
421
- return path.join(homedir(), ".openclaw");
422
- }
423
-
424
- /**
425
- * Resolve OpenClaw config file path (matches OpenClaw's own logic).
426
- */
427
- export function resolveConfigPath(): string {
428
- const path = require("node:path");
429
-
430
- const override =
431
- process.env.OPENCLAW_CONFIG_PATH?.trim() ||
432
- process.env.CLAWDBOT_CONFIG_PATH?.trim();
433
- if (override) {
434
- const { homedir } = require("node:os");
435
- return override.startsWith("~")
436
- ? override.replace(/^~(?=$|[\\/])/, homedir())
437
- : path.resolve(override);
438
- }
439
- return path.join(resolveStateDir(), "openclaw.json");
440
- }
441
-
442
- /**
443
- * Get the workspace directory from OpenClaw config
444
- */
445
- export function getWorkspaceDir(): string {
446
- const { homedir } = require("node:os");
447
- const fs = require("node:fs");
448
- const path = require("node:path");
449
-
450
- // Resolve config path
451
- const override =
452
- process.env.OPENCLAW_CONFIG_PATH?.trim() ||
453
- process.env.CLAWDBOT_CONFIG_PATH?.trim();
454
- let configPath: string;
455
- if (override) {
456
- configPath = override.startsWith("~")
457
- ? override.replace(/^~(?=$|[\\/])/, homedir())
458
- : path.resolve(override);
459
- } else {
460
- const stateDir =
461
- process.env.OPENCLAW_STATE_DIR?.trim() ||
462
- process.env.CLAWDBOT_STATE_DIR?.trim();
463
- const resolvedStateDir = stateDir
464
- ? stateDir.startsWith("~")
465
- ? stateDir.replace(/^~(?=$|[\\/])/, homedir())
466
- : path.resolve(stateDir)
467
- : path.join(homedir(), ".openclaw");
468
- configPath = path.join(resolvedStateDir, "openclaw.json");
469
- }
470
-
471
- // Read workspace from config
472
- if (fs.existsSync(configPath)) {
473
- try {
474
- const content = fs.readFileSync(configPath, "utf-8");
475
- const config = JSON.parse(content);
476
- const workspace = config?.agents?.defaults?.workspace;
477
- if (workspace) {
478
- return workspace.startsWith("~")
479
- ? workspace.replace(/^~(?=$|[\\/])/, homedir())
480
- : workspace;
481
- }
482
- } catch (_e) {
483
- // Fall back to default
484
- }
485
- }
486
-
487
- // Default workspace
488
- const stateDir =
489
- process.env.OPENCLAW_STATE_DIR?.trim() ||
490
- process.env.CLAWDBOT_STATE_DIR?.trim();
491
- const resolvedStateDir = stateDir
492
- ? stateDir.startsWith("~")
493
- ? stateDir.replace(/^~(?=$|[\\/])/, homedir())
494
- : path.resolve(stateDir)
495
- : path.join(homedir(), ".openclaw");
496
- return path.join(resolvedStateDir, "workspace");
497
- }
package/graph/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export { NetworkStateManager } from "./state.ts"
2
- export { registerNetworkTools } from "./tools.ts"
3
- export { scanMemories, formatScanResults, writeEntity, completeMemories, slugify } from "./ops.ts"
4
- export type { EntityType, SourceMemories, ScannedMemory, WriteEntityParams } from "./ops.ts"
5
- export { buildExtractionPrompt, getCronSetupCommand, getCronRemoveCommand, CRON_JOB_NAME } from "./cron.ts"