@gamaze/hicortex 0.5.0 → 0.5.2

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.
@@ -24,7 +24,7 @@ export declare function parseJsonLenient<T>(text: string, fallback: T): T;
24
24
  /**
25
25
  * Run the full consolidation pipeline. Returns a structured report.
26
26
  */
27
- export declare function runConsolidation(db: Database.Database, llm: LlmClient, embedFn: EmbedFn, dryRun?: boolean): Promise<ConsolidationReport>;
27
+ export declare function runConsolidation(db: Database.Database, llm: LlmClient, embedFn: EmbedFn, dryRun?: boolean, skipReflection?: boolean): Promise<ConsolidationReport>;
28
28
  /**
29
29
  * Calculate milliseconds until the next occurrence of a given hour (local time).
30
30
  */
@@ -388,7 +388,7 @@ function stageDecayPrune(db, dryRun) {
388
388
  /**
389
389
  * Run the full consolidation pipeline. Returns a structured report.
390
390
  */
391
- async function runConsolidation(db, llm, embedFn, dryRun = false) {
391
+ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection = false) {
392
392
  const start = new Date();
393
393
  const report = {
394
394
  started_at: start.toISOString(),
@@ -424,7 +424,16 @@ async function runConsolidation(db, llm, embedFn, dryRun = false) {
424
424
  // Stage 2: Importance Scoring
425
425
  report.stages.importance = await stageImportance(db, scoreMemories, llm, budget, dryRun);
426
426
  // Stage 2.5: Reflection
427
- report.stages.reflection = await stageReflection(db, precheck.newMemories, llm, budget, embedFn, dryRun);
427
+ if (skipReflection) {
428
+ report.stages.reflection = {
429
+ lessons_generated: 0,
430
+ skipped: true,
431
+ reason: "reflect_endpoint_offline",
432
+ };
433
+ }
434
+ else {
435
+ report.stages.reflection = await stageReflection(db, precheck.newMemories, llm, budget, embedFn, dryRun);
436
+ }
428
437
  // Stage 3: Link Discovery
429
438
  report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun);
430
439
  // Stage 4: Decay & Prune
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 {
@@ -270,8 +276,24 @@ async function runNightly(options = {}) {
270
276
  console.log(`[hicortex] Distillation complete: ${memoriesIngested} new memories`);
271
277
  // Step 3: Consolidation
272
278
  if (!dryRun) {
279
+ // Pre-flight health check for the reflect endpoint.
280
+ // If reflectBaseUrl points to a remote Ollama and it's down (MBP offline),
281
+ // skip reflection entirely instead of waiting through 3 retries (~3.5 min).
282
+ // Scoring + linking + decay still run (they use the local model or don't need LLM).
283
+ let skipReflection = false;
284
+ if (llmConfig.reflectBaseUrl && (llmConfig.reflectProvider ?? llmConfig.provider) === "ollama") {
285
+ const reflectModel = llmConfig.reflectModel ?? llmConfig.model;
286
+ const health = await (0, llm_js_1.probeOllamaModel)(llmConfig.reflectBaseUrl, reflectModel);
287
+ if (!health.ok) {
288
+ const reason = health.reason === "unreachable"
289
+ ? `reflect endpoint unreachable (${llmConfig.reflectBaseUrl})`
290
+ : `reflect model not loaded (${reflectModel} missing on ${llmConfig.reflectBaseUrl})`;
291
+ console.warn(`[hicortex] ${reason} — skipping reflection, scoring + linking will still run`);
292
+ skipReflection = true;
293
+ }
294
+ }
273
295
  console.log(`[hicortex] Running consolidation...`);
274
- const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun);
296
+ const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, skipReflection);
275
297
  console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
276
298
  (report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
277
299
  }
@@ -299,6 +321,22 @@ async function runNightly(options = {}) {
299
321
  }
300
322
  }
301
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
+ }
302
340
  }
303
341
  finally {
304
342
  db.close();
@@ -492,6 +530,19 @@ async function runClientNightly(config, dryRun) {
492
530
  }
493
531
  }
494
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
+ }
495
546
  }
496
547
  /**
497
548
  * Fetch lessons + memory index from server and inject into CLAUDE.md.
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
+ }
@@ -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.0",
5
+ "version": "0.5.2",
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.0",
3
+ "version": "0.5.2",
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": {