@memtensor/memos-local-openclaw-plugin 1.0.4-beta.0 → 1.0.4-beta.10

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 (98) hide show
  1. package/.env.example +7 -0
  2. package/README.md +24 -24
  3. package/dist/capture/index.d.ts +1 -1
  4. package/dist/capture/index.d.ts.map +1 -1
  5. package/dist/capture/index.js +34 -2
  6. package/dist/capture/index.js.map +1 -1
  7. package/dist/client/connector.d.ts +5 -2
  8. package/dist/client/connector.d.ts.map +1 -1
  9. package/dist/client/connector.js +173 -14
  10. package/dist/client/connector.js.map +1 -1
  11. package/dist/client/hub.d.ts.map +1 -1
  12. package/dist/client/hub.js +22 -0
  13. package/dist/client/hub.js.map +1 -1
  14. package/dist/client/skill-sync.d.ts +7 -0
  15. package/dist/client/skill-sync.d.ts.map +1 -1
  16. package/dist/client/skill-sync.js +10 -0
  17. package/dist/client/skill-sync.js.map +1 -1
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +9 -11
  20. package/dist/config.js.map +1 -1
  21. package/dist/hub/server.d.ts +7 -0
  22. package/dist/hub/server.d.ts.map +1 -1
  23. package/dist/hub/server.js +301 -106
  24. package/dist/hub/server.js.map +1 -1
  25. package/dist/hub/user-manager.d.ts +3 -0
  26. package/dist/hub/user-manager.d.ts.map +1 -1
  27. package/dist/hub/user-manager.js +18 -1
  28. package/dist/hub/user-manager.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +7 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/ingest/providers/index.d.ts.map +1 -1
  33. package/dist/ingest/providers/index.js +37 -6
  34. package/dist/ingest/providers/index.js.map +1 -1
  35. package/dist/recall/engine.d.ts.map +1 -1
  36. package/dist/recall/engine.js +91 -1
  37. package/dist/recall/engine.js.map +1 -1
  38. package/dist/shared/llm-call.d.ts +1 -0
  39. package/dist/shared/llm-call.d.ts.map +1 -1
  40. package/dist/shared/llm-call.js +82 -8
  41. package/dist/shared/llm-call.js.map +1 -1
  42. package/dist/sharing/types.d.ts +1 -1
  43. package/dist/sharing/types.d.ts.map +1 -1
  44. package/dist/skill/evolver.d.ts +2 -0
  45. package/dist/skill/evolver.d.ts.map +1 -1
  46. package/dist/skill/evolver.js +3 -0
  47. package/dist/skill/evolver.js.map +1 -1
  48. package/dist/storage/ensure-binding.d.ts +12 -0
  49. package/dist/storage/ensure-binding.d.ts.map +1 -0
  50. package/dist/storage/ensure-binding.js +53 -0
  51. package/dist/storage/ensure-binding.js.map +1 -0
  52. package/dist/storage/sqlite.d.ts +74 -20
  53. package/dist/storage/sqlite.d.ts.map +1 -1
  54. package/dist/storage/sqlite.js +301 -207
  55. package/dist/storage/sqlite.js.map +1 -1
  56. package/dist/telemetry.d.ts +12 -5
  57. package/dist/telemetry.d.ts.map +1 -1
  58. package/dist/telemetry.js +156 -40
  59. package/dist/telemetry.js.map +1 -1
  60. package/dist/tools/memory-search.d.ts +3 -1
  61. package/dist/tools/memory-search.d.ts.map +1 -1
  62. package/dist/tools/memory-search.js +3 -1
  63. package/dist/tools/memory-search.js.map +1 -1
  64. package/dist/types.d.ts +1 -2
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js.map +1 -1
  67. package/dist/viewer/html.d.ts.map +1 -1
  68. package/dist/viewer/html.js +2991 -1041
  69. package/dist/viewer/html.js.map +1 -1
  70. package/dist/viewer/server.d.ts +32 -8
  71. package/dist/viewer/server.d.ts.map +1 -1
  72. package/dist/viewer/server.js +1122 -261
  73. package/dist/viewer/server.js.map +1 -1
  74. package/index.ts +384 -43
  75. package/openclaw.plugin.json +1 -1
  76. package/package.json +3 -2
  77. package/scripts/postinstall.cjs +1 -1
  78. package/skill/memos-memory-guide/SKILL.md +64 -26
  79. package/src/capture/index.ts +37 -1
  80. package/src/client/connector.ts +173 -16
  81. package/src/client/hub.ts +18 -0
  82. package/src/client/skill-sync.ts +14 -0
  83. package/src/config.ts +9 -11
  84. package/src/hub/server.ts +285 -98
  85. package/src/hub/user-manager.ts +20 -3
  86. package/src/index.ts +10 -2
  87. package/src/ingest/providers/index.ts +41 -7
  88. package/src/recall/engine.ts +84 -1
  89. package/src/shared/llm-call.ts +97 -9
  90. package/src/sharing/types.ts +1 -1
  91. package/src/skill/evolver.ts +5 -0
  92. package/src/storage/ensure-binding.ts +52 -0
  93. package/src/storage/sqlite.ts +310 -233
  94. package/src/telemetry.ts +172 -41
  95. package/src/tools/memory-search.ts +2 -1
  96. package/src/types.ts +1 -2
  97. package/src/viewer/html.ts +2991 -1041
  98. package/src/viewer/server.ts +984 -190
package/src/telemetry.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Telemetry module — anonymous usage analytics via PostHog.
2
+ * Telemetry module — anonymous usage analytics via Aliyun ARMS RUM.
3
3
  *
4
4
  * Privacy-first design:
5
5
  * - Enabled by default with anonymous data only; opt-out via TELEMETRY_ENABLED=false
@@ -8,7 +8,6 @@
8
8
  * - Only sends aggregate counts, tool names, latencies, and version info
9
9
  */
10
10
 
11
- import { PostHog } from "posthog-node";
12
11
  import * as fs from "fs";
13
12
  import * as path from "path";
14
13
  import * as os from "os";
@@ -17,45 +16,79 @@ import type { Logger } from "./types";
17
16
 
18
17
  export interface TelemetryConfig {
19
18
  enabled?: boolean;
20
- posthogApiKey?: string;
21
- posthogHost?: string;
22
19
  }
23
20
 
24
- const DEFAULT_POSTHOG_API_KEY = "phc_7lae6UC5jyImDefX6uub7zCxWyswCGNoBifCKqjvDrI";
25
- const DEFAULT_POSTHOG_HOST = "https://eu.i.posthog.com";
21
+ function loadTelemetryCredentials(): { endpoint: string; pid: string; env: string } {
22
+ if (process.env.MEMOS_ARMS_ENDPOINT) {
23
+ return {
24
+ endpoint: process.env.MEMOS_ARMS_ENDPOINT,
25
+ pid: process.env.MEMOS_ARMS_PID ?? "",
26
+ env: process.env.MEMOS_ARMS_ENV ?? "prod",
27
+ };
28
+ }
29
+ try {
30
+ const credPath = path.resolve(__dirname, "..", "telemetry.credentials.json");
31
+ const raw = fs.readFileSync(credPath, "utf-8");
32
+ const creds = JSON.parse(raw);
33
+ if (creds.endpoint) return { endpoint: creds.endpoint, pid: creds.pid ?? "", env: creds.env ?? "prod" };
34
+ } catch {}
35
+ return { endpoint: "", pid: "", env: "prod" };
36
+ }
37
+
38
+ const _creds = loadTelemetryCredentials();
39
+ const ARMS_ENDPOINT = _creds.endpoint;
40
+ const ARMS_PID = _creds.pid;
41
+ const ARMS_ENV = _creds.env;
42
+
43
+ const FLUSH_AT = 10;
44
+ const FLUSH_INTERVAL_MS = 30_000;
45
+ const SEND_TIMEOUT_MS = 30_000;
46
+ const SESSION_TTL_MS = 30 * 60_000; // 30 min inactivity → new session
47
+ interface ArmsEvent {
48
+ event_type: "custom";
49
+ type: string;
50
+ name: string;
51
+ group: string;
52
+ value: number;
53
+ properties: Record<string, string | number | boolean>;
54
+ timestamp: number;
55
+ event_id: string;
56
+ times: number;
57
+ }
26
58
 
27
59
  export class Telemetry {
28
- private client: PostHog | null = null;
29
60
  private distinctId: string;
30
61
  private enabled: boolean;
31
62
  private pluginVersion: string;
32
63
  private log: Logger;
33
64
  private dailyPingSent = false;
34
65
  private dailyPingDate = "";
66
+ private buffer: ArmsEvent[] = [];
67
+ private flushTimer: ReturnType<typeof setInterval> | null = null;
68
+ private sessionId: string;
69
+ private firstSeenDate: string;
35
70
 
36
71
  constructor(config: TelemetryConfig, stateDir: string, pluginVersion: string, log: Logger) {
37
72
  this.log = log;
38
73
  this.pluginVersion = pluginVersion;
39
74
  this.enabled = config.enabled !== false;
40
75
  this.distinctId = this.loadOrCreateAnonymousId(stateDir);
76
+ this.firstSeenDate = this.loadOrCreateFirstSeen(stateDir);
77
+ this.sessionId = this.loadOrCreateSessionId(stateDir);
41
78
 
42
- if (!this.enabled) {
43
- this.log.debug("Telemetry disabled (opt-out via TELEMETRY_ENABLED=false)");
79
+ if (!this.enabled || !ARMS_ENDPOINT) {
80
+ this.enabled = false;
81
+ this.log.debug(
82
+ !ARMS_ENDPOINT
83
+ ? "Telemetry disabled (no credentials configured)"
84
+ : "Telemetry disabled (opt-out)",
85
+ );
44
86
  return;
45
87
  }
46
88
 
47
- const apiKey = config.posthogApiKey || DEFAULT_POSTHOG_API_KEY;
48
- try {
49
- this.client = new PostHog(apiKey, {
50
- host: config.posthogHost || DEFAULT_POSTHOG_HOST,
51
- flushAt: 10,
52
- flushInterval: 30_000,
53
- });
54
- this.log.debug("Telemetry initialized (PostHog)");
55
- } catch (err) {
56
- this.log.warn(`Telemetry init failed: ${err}`);
57
- this.enabled = false;
58
- }
89
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
90
+ if (this.flushTimer.unref) this.flushTimer.unref();
91
+ this.log.debug("Telemetry initialized (ARMS)");
59
92
  }
60
93
 
61
94
  private loadOrCreateAnonymousId(stateDir: string): string {
@@ -81,24 +114,113 @@ export class Telemetry {
81
114
  return newId;
82
115
  }
83
116
 
117
+ private loadOrCreateSessionId(stateDir: string): string {
118
+ const filePath = path.join(stateDir, "memos-local", ".session");
119
+ try {
120
+ const raw = fs.readFileSync(filePath, "utf-8").trim();
121
+ const sep = raw.indexOf("|");
122
+ if (sep > 0) {
123
+ const ts = parseInt(raw.slice(0, sep), 10);
124
+ const id = raw.slice(sep + 1);
125
+ if (id.length > 10 && Date.now() - ts < SESSION_TTL_MS) {
126
+ this.touchSession(filePath, id);
127
+ return id;
128
+ }
129
+ }
130
+ } catch {}
131
+ const newId = uuidv4();
132
+ this.touchSession(filePath, newId);
133
+ return newId;
134
+ }
135
+
136
+ private touchSession(filePath: string, id: string): void {
137
+ try {
138
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
139
+ fs.writeFileSync(filePath, `${Date.now()}|${id}`, "utf-8");
140
+ } catch {}
141
+ }
142
+
143
+ private loadOrCreateFirstSeen(stateDir: string): string {
144
+ const filePath = path.join(stateDir, "memos-local", ".first-seen");
145
+ try {
146
+ const existing = fs.readFileSync(filePath, "utf-8").trim();
147
+ if (existing.length === 10) return existing;
148
+ } catch {}
149
+ const today = new Date().toISOString().slice(0, 10);
150
+ try {
151
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
152
+ fs.writeFileSync(filePath, today, "utf-8");
153
+ } catch {}
154
+ return today;
155
+ }
156
+
84
157
  private capture(event: string, properties?: Record<string, unknown>): void {
85
- if (!this.enabled || !this.client) return;
158
+ if (!this.enabled) return;
159
+
160
+ const safeProps: Record<string, string | number | boolean> = {
161
+ plugin_version: this.pluginVersion,
162
+ os: os.platform(),
163
+ os_version: os.release(),
164
+ node_version: process.version,
165
+ arch: os.arch(),
166
+ };
167
+ if (properties) {
168
+ for (const [k, v] of Object.entries(properties)) {
169
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
170
+ safeProps[k] = v;
171
+ }
172
+ }
173
+ }
174
+
175
+ this.buffer.push({
176
+ event_type: "custom",
177
+ type: "memos_plugin",
178
+ name: event,
179
+ group: "memos_local",
180
+ value: 1,
181
+ properties: safeProps,
182
+ timestamp: Date.now(),
183
+ event_id: uuidv4(),
184
+ times: 1,
185
+ });
186
+
187
+ if (this.buffer.length >= FLUSH_AT) {
188
+ this.flush();
189
+ }
190
+ }
191
+
192
+ private buildPayload(events: ArmsEvent[]): Record<string, unknown> {
193
+ return {
194
+ app: {
195
+ id: ARMS_PID,
196
+ env: ARMS_ENV,
197
+ version: this.pluginVersion,
198
+ type: "node",
199
+ },
200
+ user: { id: this.distinctId },
201
+ session: { id: this.sessionId },
202
+ net: {},
203
+ view: { id: "plugin", name: "memos-local-openclaw" },
204
+ events,
205
+ _v: "1.0.0",
206
+ };
207
+ }
208
+
209
+ private async flush(): Promise<void> {
210
+ if (this.buffer.length === 0) return;
211
+ const batch = this.buffer.splice(0);
212
+ const payload = this.buildPayload(batch);
86
213
 
87
214
  try {
88
- this.client.capture({
89
- distinctId: this.distinctId,
90
- event,
91
- properties: {
92
- plugin_version: this.pluginVersion,
93
- os: os.platform(),
94
- os_version: os.release(),
95
- node_version: process.version,
96
- arch: os.arch(),
97
- ...properties,
98
- },
215
+ const resp = await fetch(ARMS_ENDPOINT, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "text/plain" },
218
+ body: JSON.stringify(payload),
219
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
99
220
  });
100
- } catch {
101
- // best-effort, never throw
221
+ this.log.debug(`Telemetry flush: ${batch.length} events → ${resp.status}`);
222
+ } catch (err) {
223
+ this.log.debug(`Telemetry flush failed: ${err}`);
102
224
  }
103
225
  }
104
226
 
@@ -131,7 +253,7 @@ export class Telemetry {
131
253
  });
132
254
  }
133
255
 
134
- trackSkillEvolved(skillName: string, upgradeType: string): void {
256
+ trackSkillEvolved(skillName: string, upgradeType: "created" | "upgraded"): void {
135
257
  this.capture("skill_evolved", {
136
258
  skill_name: skillName,
137
259
  upgrade_type: upgradeType,
@@ -150,19 +272,28 @@ export class Telemetry {
150
272
  });
151
273
  }
152
274
 
275
+ trackError(source: string, errorType: string): void {
276
+ this.capture("plugin_error", {
277
+ error_source: source,
278
+ error_type: errorType,
279
+ });
280
+ }
281
+
153
282
  private maybeSendDailyPing(): void {
154
283
  const today = new Date().toISOString().slice(0, 10);
155
284
  if (this.dailyPingSent && this.dailyPingDate === today) return;
156
285
  this.dailyPingSent = true;
157
286
  this.dailyPingDate = today;
158
- this.capture("daily_active");
287
+ this.capture("daily_active", {
288
+ first_seen_date: this.firstSeenDate,
289
+ });
159
290
  }
160
291
 
161
292
  async shutdown(): Promise<void> {
162
- if (this.client) {
163
- try {
164
- await this.client.shutdown();
165
- } catch {}
293
+ if (this.flushTimer) {
294
+ clearInterval(this.flushTimer);
295
+ this.flushTimer = null;
166
296
  }
297
+ await this.flush();
167
298
  }
168
299
  }
@@ -24,7 +24,7 @@ function emptyHubResult(scope: HubScope): HubSearchResult {
24
24
  };
25
25
  }
26
26
 
27
- export function createMemorySearchTool(engine: RecallEngine, store?: SqliteStore, ctx?: PluginContext): ToolDefinition {
27
+ export function createMemorySearchTool(engine: RecallEngine, store?: SqliteStore, ctx?: PluginContext, sharedState?: { lastSearchTime: number }): ToolDefinition {
28
28
  return {
29
29
  name: "memory_search",
30
30
  description:
@@ -60,6 +60,7 @@ export function createMemorySearchTool(engine: RecallEngine, store?: SqliteStore
60
60
  },
61
61
  },
62
62
  handler: async (input) => {
63
+ if (sharedState) sharedState.lastSearchTime = Date.now();
63
64
  const query = (input.query as string) ?? "";
64
65
  const maxResults = input.maxResults as number | undefined;
65
66
  const minScore = input.minScore as number | undefined;
package/src/types.ts CHANGED
@@ -255,8 +255,6 @@ export interface SkillEvolutionConfig {
255
255
 
256
256
  export interface TelemetryConfig {
257
257
  enabled?: boolean;
258
- posthogApiKey?: string;
259
- posthogHost?: string;
260
258
  }
261
259
 
262
260
  export type SharingRole = "hub" | "client";
@@ -277,6 +275,7 @@ export interface ClientModeConfig {
277
275
  hubAddress?: string;
278
276
  userToken?: string;
279
277
  teamToken?: string;
278
+ nickname?: string;
280
279
  pendingUserId?: string;
281
280
  }
282
281