@gamaze/hicortex 0.5.1 → 0.5.3
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/dist/consolidate.js +35 -7
- package/dist/distiller.d.ts +7 -2
- package/dist/distiller.js +16 -3
- package/dist/nightly.js +35 -0
- package/dist/redact.d.ts +45 -0
- package/dist/redact.js +101 -0
- package/dist/state.d.ts +2 -0
- package/dist/telemetry.d.ts +48 -0
- package/dist/telemetry.js +77 -0
- package/dist/types.d.ts +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/consolidate.js
CHANGED
|
@@ -273,14 +273,42 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
|
|
|
273
273
|
break;
|
|
274
274
|
}
|
|
275
275
|
const embedding = await embedFn(content);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
// Contradiction check: find semantically similar existing lessons.
|
|
277
|
+
// If a very similar lesson exists, ask the LLM whether the new one
|
|
278
|
+
// contradicts it. If yes, suppress the new lesson to prevent the
|
|
279
|
+
// "false coherence" failure mode (wrong lessons reinforcing themselves).
|
|
280
|
+
const similarLessons = storage.vectorSearch(db, embedding, 3)
|
|
281
|
+
.filter((n) => {
|
|
282
|
+
const sim = 1.0 - n.distance;
|
|
283
|
+
return sim > 0.80 && n.memory_type === "lesson";
|
|
282
284
|
});
|
|
283
|
-
|
|
285
|
+
let contradicted = false;
|
|
286
|
+
if (similarLessons.length > 0 && budget.use("contradiction_check")) {
|
|
287
|
+
const existingText = similarLessons[0].content.slice(0, 300);
|
|
288
|
+
const newText = content.slice(0, 300);
|
|
289
|
+
try {
|
|
290
|
+
const verdict = await llm.completeFast(`Two lessons from an AI memory system. Do they CONTRADICT each other (opposite advice on the same topic)?\n\n` +
|
|
291
|
+
`EXISTING: ${existingText}\n\nNEW: ${newText}\n\n` +
|
|
292
|
+
`Answer ONLY "yes" or "no". If the new lesson updates/refines the existing one (not contradicts), answer "no".`, 16);
|
|
293
|
+
if (verdict.toLowerCase().trim().startsWith("yes")) {
|
|
294
|
+
contradicted = true;
|
|
295
|
+
console.log(`[hicortex] Lesson suppressed (contradicts existing): "${lessonText.slice(0, 80)}"`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// LLM call failed — don't suppress, store the lesson
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!contradicted) {
|
|
303
|
+
storage.insertMemory(db, content, embedding, {
|
|
304
|
+
sourceAgent: "hicortex/reflection",
|
|
305
|
+
project,
|
|
306
|
+
memoryType: "lesson",
|
|
307
|
+
baseStrength: baseStrength[severity] ?? 0.8,
|
|
308
|
+
privacy: "WORK",
|
|
309
|
+
});
|
|
310
|
+
generated++;
|
|
311
|
+
}
|
|
284
312
|
}
|
|
285
313
|
catch {
|
|
286
314
|
// Failed to store lesson
|
package/dist/distiller.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* not from filesystem scanning.
|
|
5
5
|
*/
|
|
6
6
|
import type { LlmClient } from "./llm.js";
|
|
7
|
+
import { type RedactionConfig } from "./redact.js";
|
|
7
8
|
/**
|
|
8
9
|
* Estimate a safe chunk size in chars based on the LLM provider and model.
|
|
9
10
|
* - API providers (Anthropic, OpenAI, claude-cli): no chunking needed (large context windows)
|
|
@@ -14,9 +15,13 @@ import type { LlmClient } from "./llm.js";
|
|
|
14
15
|
*/
|
|
15
16
|
export declare function detectChunkSize(provider: string, model: string, baseUrl?: string): Promise<number>;
|
|
16
17
|
/**
|
|
17
|
-
* Convert
|
|
18
|
+
* Convert session messages to a filtered transcript string.
|
|
19
|
+
* Handles OC hook format, CC JSONL, and Pi JSONL.
|
|
20
|
+
*
|
|
21
|
+
* If redactionConfig is provided (or defaults to enabled), secrets and PII
|
|
22
|
+
* are scrubbed from the final text BEFORE it reaches any LLM or storage.
|
|
18
23
|
*/
|
|
19
|
-
export declare function extractConversationText(messages: unknown[]): string;
|
|
24
|
+
export declare function extractConversationText(messages: unknown[], redactionConfig?: RedactionConfig): string;
|
|
20
25
|
/**
|
|
21
26
|
* Send filtered conversation to LLM for knowledge extraction.
|
|
22
27
|
* For large transcripts, chunks into segments to avoid overwhelming small models.
|
package/dist/distiller.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.detectChunkSize = detectChunkSize;
|
|
|
9
9
|
exports.extractConversationText = extractConversationText;
|
|
10
10
|
exports.distillSession = distillSession;
|
|
11
11
|
const prompts_js_1 = require("./prompts.js");
|
|
12
|
+
const redact_js_1 = require("./redact.js");
|
|
12
13
|
const MAX_TRANSCRIPT_CHARS = 80_000;
|
|
13
14
|
const MIN_CONVERSATION_CHARS = 200;
|
|
14
15
|
// Chunk size limits by model parameter count (for local/CPU inference)
|
|
@@ -162,9 +163,13 @@ function cleanMessageContent(text) {
|
|
|
162
163
|
return text.trim();
|
|
163
164
|
}
|
|
164
165
|
/**
|
|
165
|
-
* Convert
|
|
166
|
+
* Convert session messages to a filtered transcript string.
|
|
167
|
+
* Handles OC hook format, CC JSONL, and Pi JSONL.
|
|
168
|
+
*
|
|
169
|
+
* If redactionConfig is provided (or defaults to enabled), secrets and PII
|
|
170
|
+
* are scrubbed from the final text BEFORE it reaches any LLM or storage.
|
|
166
171
|
*/
|
|
167
|
-
function extractConversationText(messages) {
|
|
172
|
+
function extractConversationText(messages, redactionConfig) {
|
|
168
173
|
const parts = [];
|
|
169
174
|
for (const msg of messages) {
|
|
170
175
|
if (typeof msg !== "object" || msg === null)
|
|
@@ -196,7 +201,15 @@ function extractConversationText(messages) {
|
|
|
196
201
|
const role = msgRole === "user" ? "USER" : "ASSISTANT";
|
|
197
202
|
parts.push(`${role}: ${text}`);
|
|
198
203
|
}
|
|
199
|
-
|
|
204
|
+
let result = parts.join("\n\n");
|
|
205
|
+
// Redact secrets and PII before the text reaches any LLM or storage.
|
|
206
|
+
// This is the last step — after all cleaning/filtering but before return.
|
|
207
|
+
const { text: redacted, count } = (0, redact_js_1.redact)(result, redactionConfig);
|
|
208
|
+
if (count > 0) {
|
|
209
|
+
console.log(`[hicortex] Redacted ${count} secret(s) from transcript`);
|
|
210
|
+
}
|
|
211
|
+
result = redacted;
|
|
212
|
+
return result;
|
|
200
213
|
}
|
|
201
214
|
/**
|
|
202
215
|
* Send filtered conversation to LLM for knowledge extraction.
|
package/dist/nightly.js
CHANGED
|
@@ -47,6 +47,11 @@ exports.runNightly = runNightly;
|
|
|
47
47
|
const node_fs_1 = require("node:fs");
|
|
48
48
|
const node_path_1 = require("node:path");
|
|
49
49
|
const node_os_1 = require("node:os");
|
|
50
|
+
let VERSION = "0.0.0";
|
|
51
|
+
try {
|
|
52
|
+
VERSION = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8")).version;
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
50
55
|
const db_js_1 = require("./db.js");
|
|
51
56
|
const llm_js_1 = require("./llm.js");
|
|
52
57
|
const embedder_js_1 = require("./embedder.js");
|
|
@@ -59,6 +64,7 @@ const claude_md_js_1 = require("./claude-md.js");
|
|
|
59
64
|
const features_js_1 = require("./features.js");
|
|
60
65
|
const extensions_js_1 = require("./extensions.js");
|
|
61
66
|
const state_js_1 = require("./state.js");
|
|
67
|
+
const telemetry_js_1 = require("./telemetry.js");
|
|
62
68
|
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
63
69
|
function readNightlyConfig(stateDir) {
|
|
64
70
|
try {
|
|
@@ -315,6 +321,22 @@ async function runNightly(options = {}) {
|
|
|
315
321
|
}
|
|
316
322
|
}
|
|
317
323
|
console.log(`[hicortex] Nightly pipeline complete.`);
|
|
324
|
+
// Step 6: Anonymous telemetry (fire-and-forget, opt-out via config)
|
|
325
|
+
if (!dryRun && (0, telemetry_js_1.isTelemetryEnabled)(savedConfig)) {
|
|
326
|
+
const agentType = piBatches.length > 0 && ccBatches.length > 0 ? "mixed"
|
|
327
|
+
: piBatches.length > 0 ? "pi"
|
|
328
|
+
: "cc";
|
|
329
|
+
await (0, telemetry_js_1.sendTelemetry)({
|
|
330
|
+
id: (0, telemetry_js_1.getTelemetryId)(stateDir),
|
|
331
|
+
v: VERSION,
|
|
332
|
+
mode: "server",
|
|
333
|
+
agent: agentType,
|
|
334
|
+
mem: storage.countMemories(db),
|
|
335
|
+
lessons: storage.getLessons(db, 365).length,
|
|
336
|
+
sessions: batches.length,
|
|
337
|
+
ok: !hadTransientFailure,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
318
340
|
}
|
|
319
341
|
finally {
|
|
320
342
|
db.close();
|
|
@@ -508,6 +530,19 @@ async function runClientNightly(config, dryRun) {
|
|
|
508
530
|
}
|
|
509
531
|
}
|
|
510
532
|
console.log(`[hicortex] Client nightly complete: ${memoriesIngested} memories from ${sessionsSent} sessions → ${serverUrl}`);
|
|
533
|
+
// Anonymous telemetry (fire-and-forget, opt-out via config)
|
|
534
|
+
if (!dryRun && (0, telemetry_js_1.isTelemetryEnabled)(config)) {
|
|
535
|
+
await (0, telemetry_js_1.sendTelemetry)({
|
|
536
|
+
id: (0, telemetry_js_1.getTelemetryId)(HICORTEX_HOME),
|
|
537
|
+
v: VERSION,
|
|
538
|
+
mode: "client",
|
|
539
|
+
agent: "cc", // client mode is always CC-originated currently
|
|
540
|
+
mem: memoriesIngested,
|
|
541
|
+
lessons: 0, // client doesn't know lesson count
|
|
542
|
+
sessions: batches.length,
|
|
543
|
+
ok: !hadTransientFailure,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
511
546
|
}
|
|
512
547
|
/**
|
|
513
548
|
* Fetch lessons + memory index from server and inject into CLAUDE.md.
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-ingestion redaction — scrubs secrets and PII from transcript text
|
|
3
|
+
* BEFORE it reaches the distillation LLM or storage.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - Session transcripts contain tool output: file reads, command output,
|
|
7
|
+
* env var dumps. These regularly contain API keys, tokens, and paths.
|
|
8
|
+
* - The distillation LLM is often remote (e.g., Ollama on MBP via
|
|
9
|
+
* Tailscale). Secrets in the transcript travel over the network.
|
|
10
|
+
* - Even if the LLM correctly classifies the memory as SENSITIVE, the
|
|
11
|
+
* secret is already stored and searchable via hicortex_search.
|
|
12
|
+
* - Redaction runs BEFORE the LLM sees the text, eliminating the risk.
|
|
13
|
+
*
|
|
14
|
+
* Default patterns cover common API key formats, bearer tokens, absolute
|
|
15
|
+
* paths, and generic key=value secrets. Users can add custom patterns via
|
|
16
|
+
* config.json "redaction.extraPatterns".
|
|
17
|
+
*
|
|
18
|
+
* The replacement is always [REDACTED] (or configurable). This preserves
|
|
19
|
+
* the structure of the text so the LLM can still extract useful knowledge
|
|
20
|
+
* from the surrounding context.
|
|
21
|
+
*/
|
|
22
|
+
/** Result of a redaction pass. */
|
|
23
|
+
export interface RedactionResult {
|
|
24
|
+
/** The redacted text. */
|
|
25
|
+
text: string;
|
|
26
|
+
/** Number of individual redactions applied. */
|
|
27
|
+
count: number;
|
|
28
|
+
}
|
|
29
|
+
/** Configuration for redaction, read from config.json. */
|
|
30
|
+
export interface RedactionConfig {
|
|
31
|
+
/** Master switch. Default: true. */
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/** Additional regex patterns (strings, compiled to RegExp with 'g' flag). */
|
|
34
|
+
extraPatterns?: string[];
|
|
35
|
+
/** Replacement string. Default: "[REDACTED]". */
|
|
36
|
+
replacement?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Redact secrets and PII from text.
|
|
40
|
+
*
|
|
41
|
+
* @param text The raw transcript text to redact
|
|
42
|
+
* @param config Optional configuration (extra patterns, replacement string)
|
|
43
|
+
* @returns The redacted text and count of redactions applied
|
|
44
|
+
*/
|
|
45
|
+
export declare function redact(text: string, config?: RedactionConfig): RedactionResult;
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pre-ingestion redaction — scrubs secrets and PII from transcript text
|
|
4
|
+
* BEFORE it reaches the distillation LLM or storage.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists:
|
|
7
|
+
* - Session transcripts contain tool output: file reads, command output,
|
|
8
|
+
* env var dumps. These regularly contain API keys, tokens, and paths.
|
|
9
|
+
* - The distillation LLM is often remote (e.g., Ollama on MBP via
|
|
10
|
+
* Tailscale). Secrets in the transcript travel over the network.
|
|
11
|
+
* - Even if the LLM correctly classifies the memory as SENSITIVE, the
|
|
12
|
+
* secret is already stored and searchable via hicortex_search.
|
|
13
|
+
* - Redaction runs BEFORE the LLM sees the text, eliminating the risk.
|
|
14
|
+
*
|
|
15
|
+
* Default patterns cover common API key formats, bearer tokens, absolute
|
|
16
|
+
* paths, and generic key=value secrets. Users can add custom patterns via
|
|
17
|
+
* config.json "redaction.extraPatterns".
|
|
18
|
+
*
|
|
19
|
+
* The replacement is always [REDACTED] (or configurable). This preserves
|
|
20
|
+
* the structure of the text so the LLM can still extract useful knowledge
|
|
21
|
+
* from the surrounding context.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.redact = redact;
|
|
25
|
+
/**
|
|
26
|
+
* Default redaction patterns. Each targets a specific class of secret.
|
|
27
|
+
* Order matters: more specific patterns should come first to avoid
|
|
28
|
+
* partial matches by generic patterns.
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_PATTERNS = [
|
|
31
|
+
// Anthropic API keys: sk-ant-api03-...
|
|
32
|
+
{ name: "anthropic_key", pattern: /sk-ant-[a-zA-Z0-9\-_]{20,}/g },
|
|
33
|
+
// OpenAI API keys: sk-proj-... or sk-...
|
|
34
|
+
{ name: "openai_key", pattern: /sk-(?:proj-)?[a-zA-Z0-9]{20,}/g },
|
|
35
|
+
// Hicortex license keys: hctx-... (case-insensitive — keys could appear uppercased in logs)
|
|
36
|
+
{ name: "hicortex_key", pattern: /hctx-[a-f0-9]{16}/gi },
|
|
37
|
+
// GitHub Personal Access Tokens: ghp_...
|
|
38
|
+
{ name: "github_pat", pattern: /ghp_[a-zA-Z0-9]{36}/g },
|
|
39
|
+
// GitHub OAuth tokens: gho_...
|
|
40
|
+
{ name: "github_oauth", pattern: /gho_[a-zA-Z0-9]{36}/g },
|
|
41
|
+
// Google API keys: AIza...
|
|
42
|
+
{ name: "google_key", pattern: /AIza[a-zA-Z0-9_\-]{35}/g },
|
|
43
|
+
// AWS access keys: AKIA...
|
|
44
|
+
{ name: "aws_key", pattern: /AKIA[A-Z0-9]{16}/g },
|
|
45
|
+
// Stripe live/test keys: sk_live_..., sk_test_...
|
|
46
|
+
{ name: "stripe_key", pattern: /sk_(?:live|test)_[a-zA-Z0-9]{20,}/g },
|
|
47
|
+
// Bearer tokens in headers (case-insensitive — headers are case-insensitive)
|
|
48
|
+
{ name: "bearer_token", pattern: /[Bb]earer\s+[a-zA-Z0-9._\-]{20,}/g },
|
|
49
|
+
// Generic secret assignments: password=..., secret_key=..., token: ...
|
|
50
|
+
// Matches key=value and key: value patterns with common secret key names.
|
|
51
|
+
// The key name can have underscores/hyphens and optional suffixes (SECRET_KEY, api-key, etc.)
|
|
52
|
+
// Negative lookahead for [REDACTED] prevents double-counting when a prior pattern
|
|
53
|
+
// already replaced the value (e.g., bearer_token fires, then generic_secret sees
|
|
54
|
+
// "token: [REDACTED]" and would otherwise match again).
|
|
55
|
+
{ name: "generic_secret", pattern: /(?:password|secret(?:[_-]?key)?|token|api[_-]?key|private[_-]?key|access[_-]?key)\s*[:=]\s*["']?(?!\[REDACTED\])[^\s"']{8,}["']?/gi },
|
|
56
|
+
// Absolute macOS paths: /Users/<username>/...
|
|
57
|
+
// Negative lookbehind avoids matching URL paths like https://api.example.com/Users/list
|
|
58
|
+
{ name: "macos_path", pattern: /(?<![:/])\/Users\/[a-zA-Z0-9._-]+/g },
|
|
59
|
+
// Absolute Linux home paths: /home/<username>/...
|
|
60
|
+
// Same lookbehind to avoid URL false positives
|
|
61
|
+
{ name: "linux_path", pattern: /(?<![:/])\/home\/[a-zA-Z0-9._-]+/g },
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Redact secrets and PII from text.
|
|
65
|
+
*
|
|
66
|
+
* @param text The raw transcript text to redact
|
|
67
|
+
* @param config Optional configuration (extra patterns, replacement string)
|
|
68
|
+
* @returns The redacted text and count of redactions applied
|
|
69
|
+
*/
|
|
70
|
+
function redact(text, config) {
|
|
71
|
+
if (config?.enabled === false)
|
|
72
|
+
return { text, count: 0 };
|
|
73
|
+
const replacement = config?.replacement ?? "[REDACTED]";
|
|
74
|
+
let count = 0;
|
|
75
|
+
let result = text;
|
|
76
|
+
// Apply default patterns
|
|
77
|
+
for (const { pattern } of DEFAULT_PATTERNS) {
|
|
78
|
+
// Reset lastIndex for global regexes (they're stateful)
|
|
79
|
+
pattern.lastIndex = 0;
|
|
80
|
+
result = result.replace(pattern, () => {
|
|
81
|
+
count++;
|
|
82
|
+
return replacement;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Apply user-configured extra patterns
|
|
86
|
+
if (config?.extraPatterns) {
|
|
87
|
+
for (const patternStr of config.extraPatterns) {
|
|
88
|
+
try {
|
|
89
|
+
const re = new RegExp(patternStr, "g");
|
|
90
|
+
result = result.replace(re, () => {
|
|
91
|
+
count++;
|
|
92
|
+
return replacement;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Invalid regex — skip silently (don't crash the pipeline)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { text: result, count };
|
|
101
|
+
}
|
package/dist/state.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface HicortexState {
|
|
|
33
33
|
lastConsolidated?: string;
|
|
34
34
|
/** Last-known license tier (replaces tier.json + license-validated.txt). */
|
|
35
35
|
tier?: PersistedTier;
|
|
36
|
+
/** Anonymous telemetry UUID — generated once, never linked to personal info. */
|
|
37
|
+
telemetryId?: string;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Load the state file. Returns an empty state if the file is missing
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous telemetry — sends aggregate stats after each nightly run.
|
|
3
|
+
*
|
|
4
|
+
* What's sent (8 fields, all aggregate):
|
|
5
|
+
* id — random UUID, generated once on first run, stored in state.json
|
|
6
|
+
* v — package version
|
|
7
|
+
* mode — server or client
|
|
8
|
+
* agent — cc, pi, oc, or mixed (detected from session sources)
|
|
9
|
+
* mem — total memory count
|
|
10
|
+
* lessons — total lesson count
|
|
11
|
+
* sessions — sessions distilled this run
|
|
12
|
+
* ok — nightly succeeded (true/false)
|
|
13
|
+
*
|
|
14
|
+
* What's NOT sent:
|
|
15
|
+
* No personal data, no session content, no file paths, no IPs stored.
|
|
16
|
+
*
|
|
17
|
+
* Opt-out:
|
|
18
|
+
* Set "telemetry": false in ~/.hicortex/config.json
|
|
19
|
+
* OR set HICORTEX_TELEMETRY=off in the environment
|
|
20
|
+
*
|
|
21
|
+
* The ping is fire-and-forget with a 5s timeout. If it fails, nothing
|
|
22
|
+
* happens — the nightly result is unaffected.
|
|
23
|
+
*/
|
|
24
|
+
export interface TelemetryPayload {
|
|
25
|
+
id: string;
|
|
26
|
+
v: string;
|
|
27
|
+
mode: string;
|
|
28
|
+
agent: string;
|
|
29
|
+
mem: number;
|
|
30
|
+
lessons: number;
|
|
31
|
+
sessions: number;
|
|
32
|
+
ok: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if telemetry is enabled. Disabled by:
|
|
36
|
+
* - config.telemetry === false
|
|
37
|
+
* - HICORTEX_TELEMETRY env var set to "off", "false", or "0"
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTelemetryEnabled(config: Record<string, unknown> | null): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Get or create the anonymous telemetry ID.
|
|
42
|
+
* Generated once, stored in state.json, never linked to any personal info.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getTelemetryId(stateDir: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Send anonymous telemetry. Fire-and-forget — failures are silently ignored.
|
|
47
|
+
*/
|
|
48
|
+
export declare function sendTelemetry(payload: TelemetryPayload, serverUrl?: string): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Anonymous telemetry — sends aggregate stats after each nightly run.
|
|
4
|
+
*
|
|
5
|
+
* What's sent (8 fields, all aggregate):
|
|
6
|
+
* id — random UUID, generated once on first run, stored in state.json
|
|
7
|
+
* v — package version
|
|
8
|
+
* mode — server or client
|
|
9
|
+
* agent — cc, pi, oc, or mixed (detected from session sources)
|
|
10
|
+
* mem — total memory count
|
|
11
|
+
* lessons — total lesson count
|
|
12
|
+
* sessions — sessions distilled this run
|
|
13
|
+
* ok — nightly succeeded (true/false)
|
|
14
|
+
*
|
|
15
|
+
* What's NOT sent:
|
|
16
|
+
* No personal data, no session content, no file paths, no IPs stored.
|
|
17
|
+
*
|
|
18
|
+
* Opt-out:
|
|
19
|
+
* Set "telemetry": false in ~/.hicortex/config.json
|
|
20
|
+
* OR set HICORTEX_TELEMETRY=off in the environment
|
|
21
|
+
*
|
|
22
|
+
* The ping is fire-and-forget with a 5s timeout. If it fails, nothing
|
|
23
|
+
* happens — the nightly result is unaffected.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.isTelemetryEnabled = isTelemetryEnabled;
|
|
27
|
+
exports.getTelemetryId = getTelemetryId;
|
|
28
|
+
exports.sendTelemetry = sendTelemetry;
|
|
29
|
+
const node_crypto_1 = require("node:crypto");
|
|
30
|
+
const state_js_1 = require("./state.js");
|
|
31
|
+
const TELEMETRY_URL = "https://hicortex.gamaze.com/api/telemetry";
|
|
32
|
+
/**
|
|
33
|
+
* Check if telemetry is enabled. Disabled by:
|
|
34
|
+
* - config.telemetry === false
|
|
35
|
+
* - HICORTEX_TELEMETRY env var set to "off", "false", or "0"
|
|
36
|
+
*/
|
|
37
|
+
function isTelemetryEnabled(config) {
|
|
38
|
+
// Config override
|
|
39
|
+
if (config?.telemetry === false)
|
|
40
|
+
return false;
|
|
41
|
+
// Env var override
|
|
42
|
+
const env = process.env.HICORTEX_TELEMETRY?.toLowerCase();
|
|
43
|
+
if (env === "off" || env === "false" || env === "0")
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get or create the anonymous telemetry ID.
|
|
49
|
+
* Generated once, stored in state.json, never linked to any personal info.
|
|
50
|
+
*/
|
|
51
|
+
function getTelemetryId(stateDir) {
|
|
52
|
+
const state = (0, state_js_1.loadState)(stateDir);
|
|
53
|
+
if (state.telemetryId)
|
|
54
|
+
return state.telemetryId;
|
|
55
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
56
|
+
(0, state_js_1.updateState)((s) => {
|
|
57
|
+
s.telemetryId = id;
|
|
58
|
+
return s;
|
|
59
|
+
}, stateDir);
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Send anonymous telemetry. Fire-and-forget — failures are silently ignored.
|
|
64
|
+
*/
|
|
65
|
+
async function sendTelemetry(payload, serverUrl = TELEMETRY_URL) {
|
|
66
|
+
try {
|
|
67
|
+
await fetch(serverUrl, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/json" },
|
|
70
|
+
body: JSON.stringify(payload),
|
|
71
|
+
signal: AbortSignal.timeout(5_000),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Silently ignore — telemetry must never affect the nightly result
|
|
76
|
+
}
|
|
77
|
+
}
|
package/dist/types.d.ts
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hicortex",
|
|
3
3
|
"name": "Hicortex — Long-term Memory That Learns",
|
|
4
4
|
"description": "Your agents remember past decisions, avoid repeated mistakes, and get smarter every day. Nightly reflection generates actionable lessons that automatically update agent behavior.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.3",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|