@gamaze/hicortex 0.4.6 → 0.5.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.
- package/README.md +4 -4
- package/dist/consolidate.d.ts +1 -1
- package/dist/consolidate.js +11 -2
- package/dist/distiller.js +13 -4
- package/dist/extensions.d.ts +47 -0
- package/dist/extensions.js +22 -0
- package/dist/features.d.ts +1 -1
- package/dist/features.js +16 -2
- package/dist/nightly.js +37 -9
- package/dist/pi-transcript-reader.d.ts +44 -0
- package/dist/pi-transcript-reader.js +159 -0
- package/dist/pro-loader.d.ts +33 -0
- package/dist/pro-loader.js +187 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Your agents learn from every session — successes and mistakes. Hicortex captures experiences, distills lessons, and applies them automatically. Connect multiple agents to shared memory and they improve together, overnight.
|
|
4
4
|
|
|
5
|
-
Works with **Claude Code**
|
|
5
|
+
Works with **Claude Code**, **Pi**, **OpenClaw**, and any MCP-compatible agent.
|
|
6
6
|
|
|
7
7
|
**Website:** [hicortex.gamaze.com](https://hicortex.gamaze.com) · **Docs:** [hicortex.gamaze.com/docs](https://hicortex.gamaze.com/docs/)
|
|
8
8
|
|
|
@@ -39,8 +39,8 @@ openclaw gateway restart
|
|
|
39
39
|
|
|
40
40
|
| When | What | How |
|
|
41
41
|
|------|------|-----|
|
|
42
|
-
| Agent start | Recent lessons injected into context | CLAUDE.md
|
|
43
|
-
| Agent end | Conversation captured | CC: nightly transcript scan / OC: hook |
|
|
42
|
+
| Agent start | Recent lessons injected into context | CLAUDE.md / EXPERIENCE.md / OC hook |
|
|
43
|
+
| Agent end | Conversation captured | CC + Pi: nightly transcript scan / OC: hook |
|
|
44
44
|
| Nightly | Distill → score → reflect → link → inject | Automatic pipeline |
|
|
45
45
|
|
|
46
46
|
## Agent Tools (MCP)
|
|
@@ -159,7 +159,7 @@ Canonical location: `~/.hicortex/hicortex.db`. Existing OC installations at `~/.
|
|
|
159
159
|
## Development
|
|
160
160
|
|
|
161
161
|
```bash
|
|
162
|
-
cd packages/
|
|
162
|
+
cd packages/hicortex
|
|
163
163
|
npm install
|
|
164
164
|
npm run build
|
|
165
165
|
npm test
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/consolidate.js
CHANGED
|
@@ -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
|
-
|
|
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/distiller.js
CHANGED
|
@@ -175,16 +175,25 @@ function extractConversationText(messages) {
|
|
|
175
175
|
continue;
|
|
176
176
|
if (m.isSidechain)
|
|
177
177
|
continue;
|
|
178
|
-
// Extract
|
|
179
|
-
//
|
|
180
|
-
|
|
178
|
+
// Extract the message role from whichever format we're dealing with:
|
|
179
|
+
// OC hook: m.role = "user" | "assistant"
|
|
180
|
+
// CC JSONL: m.type = "user" | "assistant"
|
|
181
|
+
// Pi JSONL: m.message.role = "user" | "assistant" | "toolResult"
|
|
182
|
+
const nestedMsg = m.message;
|
|
183
|
+
const msgRole = String(m.role ?? nestedMsg?.role ?? m.type ?? "");
|
|
184
|
+
// Skip tool results — they're noisy (file contents, command output) and
|
|
185
|
+
// add bulk without much extractable knowledge for distillation.
|
|
186
|
+
if (msgRole === "toolResult" || msgRole === "tool_result")
|
|
187
|
+
continue;
|
|
188
|
+
// Extract content — OC has content at top level; CC/Pi have message.content
|
|
189
|
+
const content = m.content ?? nestedMsg?.content;
|
|
181
190
|
if (content === undefined || content === null)
|
|
182
191
|
continue;
|
|
183
192
|
let text = extractTextFromContent(content);
|
|
184
193
|
text = cleanMessageContent(text);
|
|
185
194
|
if (text.length < 20)
|
|
186
195
|
continue;
|
|
187
|
-
const role =
|
|
196
|
+
const role = msgRole === "user" ? "USER" : "ASSISTANT";
|
|
188
197
|
parts.push(`${role}: ${text}`);
|
|
189
198
|
}
|
|
190
199
|
return parts.join("\n\n");
|
package/dist/extensions.d.ts
CHANGED
|
@@ -123,4 +123,51 @@ export declare function setExtensions(ext: Partial<typeof activeExtensions>): vo
|
|
|
123
123
|
export declare function getLessonSelector(): LessonSelector;
|
|
124
124
|
/** Get the active PromptStrategy (default unless Pro is loaded). */
|
|
125
125
|
export declare function getPromptStrategy(): PromptStrategy;
|
|
126
|
+
/**
|
|
127
|
+
* The object passed to a Pro package's `activate()` function at boot.
|
|
128
|
+
* Pro packages receive this to register their extensions and access OSS
|
|
129
|
+
* runtime APIs they need.
|
|
130
|
+
*
|
|
131
|
+
* Design rationale: Pro code never STATICALLY imports from the OSS client.
|
|
132
|
+
* All runtime access is through this context object. This has two benefits:
|
|
133
|
+
* 1. Pro bundles are self-contained — they don't have `require("../...")`
|
|
134
|
+
* calls that would break when the tarball is installed to
|
|
135
|
+
* ~/.hicortex/pro/ at runtime.
|
|
136
|
+
* 2. Pro code can only access what the OSS client exposes here, so the
|
|
137
|
+
* blast radius of a malicious/buggy Pro release is contained.
|
|
138
|
+
*
|
|
139
|
+
* Type-only imports of `LessonSelector`, `PromptStrategy` etc. in Pro code
|
|
140
|
+
* are fine — they're erased at compile time and produce no runtime imports.
|
|
141
|
+
*/
|
|
142
|
+
export interface ProActivationContext {
|
|
143
|
+
/** Register a lesson selector implementation. */
|
|
144
|
+
setSelector(selector: LessonSelector): void;
|
|
145
|
+
/** Register a prompt strategy implementation. */
|
|
146
|
+
setPrompts(prompts: PromptStrategy): void;
|
|
147
|
+
/** The version of the OSS host (from package.json). Pro can use this
|
|
148
|
+
* to gate features against host compatibility. */
|
|
149
|
+
hostVersion: string;
|
|
150
|
+
/** Log through the OSS logging surface so Pro logs get the [hicortex]
|
|
151
|
+
* prefix and unified formatting. */
|
|
152
|
+
log(message: string): void;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The shape Pro packages must export as their default export.
|
|
156
|
+
* See `packages/hicortex/src/pro/index.ts` for the reference impl.
|
|
157
|
+
*/
|
|
158
|
+
export interface ProPackage {
|
|
159
|
+
/** Called once at OSS boot if a Pro license is valid and the Pro
|
|
160
|
+
* tarball has been downloaded + extracted. Should register extensions
|
|
161
|
+
* via the context and return. Errors abort Pro activation but do not
|
|
162
|
+
* abort the OSS host. */
|
|
163
|
+
activate(ctx: ProActivationContext): void | Promise<void>;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build an activation context for a Pro package. Called from the Pro
|
|
167
|
+
* loader in features.ts / pro-loader.ts.
|
|
168
|
+
*
|
|
169
|
+
* Keep this function small — it's the ONLY surface a Pro package gets.
|
|
170
|
+
* Expanding it expands the attack surface, so add fields deliberately.
|
|
171
|
+
*/
|
|
172
|
+
export declare function createProActivationContext(hostVersion: string): ProActivationContext;
|
|
126
173
|
export {};
|
package/dist/extensions.js
CHANGED
|
@@ -34,6 +34,7 @@ exports.defaultPromptStrategy = exports.defaultLessonSelector = void 0;
|
|
|
34
34
|
exports.setExtensions = setExtensions;
|
|
35
35
|
exports.getLessonSelector = getLessonSelector;
|
|
36
36
|
exports.getPromptStrategy = getPromptStrategy;
|
|
37
|
+
exports.createProActivationContext = createProActivationContext;
|
|
37
38
|
/**
|
|
38
39
|
* Default LessonSelector — preserves current OSS behaviour exactly.
|
|
39
40
|
* `slice(0, maxLessons)` over the candidate pool, no re-ranking.
|
|
@@ -152,3 +153,24 @@ function getLessonSelector() {
|
|
|
152
153
|
function getPromptStrategy() {
|
|
153
154
|
return activeExtensions.prompts;
|
|
154
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Build an activation context for a Pro package. Called from the Pro
|
|
158
|
+
* loader in features.ts / pro-loader.ts.
|
|
159
|
+
*
|
|
160
|
+
* Keep this function small — it's the ONLY surface a Pro package gets.
|
|
161
|
+
* Expanding it expands the attack surface, so add fields deliberately.
|
|
162
|
+
*/
|
|
163
|
+
function createProActivationContext(hostVersion) {
|
|
164
|
+
return {
|
|
165
|
+
setSelector(selector) {
|
|
166
|
+
activeExtensions = { ...activeExtensions, selector };
|
|
167
|
+
},
|
|
168
|
+
setPrompts(prompts) {
|
|
169
|
+
activeExtensions = { ...activeExtensions, prompts };
|
|
170
|
+
},
|
|
171
|
+
hostVersion,
|
|
172
|
+
log(message) {
|
|
173
|
+
console.log(`[hicortex][pro] ${message}`);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
package/dist/features.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { LicenseInfo } from "./types.js";
|
|
|
22
22
|
* After this returns, sync getters (isPro, lessonsLimit, etc.) are deterministic
|
|
23
23
|
* and reflect the user's actual tier — no more "free during validation window".
|
|
24
24
|
*/
|
|
25
|
-
export declare function initFeatures(licenseKey: string | undefined, stateDir?: string): Promise<void>;
|
|
25
|
+
export declare function initFeatures(licenseKey: string | undefined, stateDir?: string, hostVersion?: string): Promise<void>;
|
|
26
26
|
/** Are we on a paid tier (Pro, Team, Lifetime)? */
|
|
27
27
|
export declare function isPro(): boolean;
|
|
28
28
|
/** Memory count cap. -1 = unlimited (paid). */
|
package/dist/features.js
CHANGED
|
@@ -54,7 +54,7 @@ function persistTier(stateDir, info) {
|
|
|
54
54
|
* After this returns, sync getters (isPro, lessonsLimit, etc.) are deterministic
|
|
55
55
|
* and reflect the user's actual tier — no more "free during validation window".
|
|
56
56
|
*/
|
|
57
|
-
async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR) {
|
|
57
|
+
async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR, hostVersion = "0.0.0") {
|
|
58
58
|
if (initialized)
|
|
59
59
|
return;
|
|
60
60
|
initialized = true;
|
|
@@ -66,7 +66,7 @@ async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR) {
|
|
|
66
66
|
else {
|
|
67
67
|
currentFeatures = FREE_FEATURES;
|
|
68
68
|
}
|
|
69
|
-
// Step 2: No key → free tier, done
|
|
69
|
+
// Step 2: No key → free tier, done. Pro loader is only run for paid tiers.
|
|
70
70
|
if (!licenseKey)
|
|
71
71
|
return;
|
|
72
72
|
// Step 3: Validate
|
|
@@ -96,6 +96,20 @@ async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR) {
|
|
|
96
96
|
// Keep persisted features
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
+
// Step 4: If the current tier is paid, try to load the Pro extension bundle.
|
|
100
|
+
// This is best-effort — if loading fails (network, missing tarball, bad
|
|
101
|
+
// extraction, Pro package throws on activate), OSS defaults remain in effect
|
|
102
|
+
// and the host keeps running. No user-visible crash.
|
|
103
|
+
if (isPro()) {
|
|
104
|
+
try {
|
|
105
|
+
const { loadPro } = await import("./pro-loader.js");
|
|
106
|
+
await loadPro(licenseKey, stateDir, hostVersion);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
+
console.warn(`[hicortex][pro] Pro loader failed to import: ${msg}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
99
113
|
}
|
|
100
114
|
// ---------------------------------------------------------------------------
|
|
101
115
|
// Public API — sync getters used everywhere in the codebase
|
package/dist/nightly.js
CHANGED
|
@@ -54,6 +54,7 @@ const storage = __importStar(require("./storage.js"));
|
|
|
54
54
|
const distiller_js_1 = require("./distiller.js");
|
|
55
55
|
const consolidate_js_1 = require("./consolidate.js");
|
|
56
56
|
const transcript_reader_js_1 = require("./transcript-reader.js");
|
|
57
|
+
const pi_transcript_reader_js_1 = require("./pi-transcript-reader.js");
|
|
57
58
|
const claude_md_js_1 = require("./claude-md.js");
|
|
58
59
|
const features_js_1 = require("./features.js");
|
|
59
60
|
const extensions_js_1 = require("./extensions.js");
|
|
@@ -171,14 +172,20 @@ async function runNightly(options = {}) {
|
|
|
171
172
|
? `${llmConfig.distillProvider}/${llmConfig.distillModel}@${llmConfig.distillBaseUrl}`
|
|
172
173
|
: llmConfig.distillModel ?? "";
|
|
173
174
|
console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model}${distillInfo ? `, distill: ${distillInfo}` : ""}`);
|
|
174
|
-
// Step 1: Read new CC
|
|
175
|
+
// Step 1: Read new transcripts (CC + Pi)
|
|
175
176
|
const since = readLastRun();
|
|
176
|
-
console.log(`[hicortex] Reading
|
|
177
|
-
const
|
|
178
|
-
|
|
177
|
+
console.log(`[hicortex] Reading transcripts since ${since.toISOString()}`);
|
|
178
|
+
const ccBatches = (0, transcript_reader_js_1.readCcTranscripts)(since);
|
|
179
|
+
const piBatches = (0, pi_transcript_reader_js_1.readPiTranscripts)(since);
|
|
180
|
+
const batches = [...ccBatches, ...piBatches];
|
|
181
|
+
if (ccBatches.length > 0)
|
|
182
|
+
console.log(`[hicortex] Found ${ccBatches.length} CC session(s)`);
|
|
183
|
+
if (piBatches.length > 0)
|
|
184
|
+
console.log(`[hicortex] Found ${piBatches.length} Pi session(s)`);
|
|
185
|
+
console.log(`[hicortex] Total: ${batches.length} new session(s)`);
|
|
179
186
|
if (batches.length === 0 && !dryRun) {
|
|
180
187
|
// Still run consolidation — there may be unscored memories from OC
|
|
181
|
-
console.log(`[hicortex] No new
|
|
188
|
+
console.log(`[hicortex] No new transcripts. Running consolidation only.`);
|
|
182
189
|
}
|
|
183
190
|
// Step 2: Distill each session
|
|
184
191
|
let memoriesIngested = 0;
|
|
@@ -263,15 +270,36 @@ async function runNightly(options = {}) {
|
|
|
263
270
|
console.log(`[hicortex] Distillation complete: ${memoriesIngested} new memories`);
|
|
264
271
|
// Step 3: Consolidation
|
|
265
272
|
if (!dryRun) {
|
|
273
|
+
// Pre-flight health check for the reflect endpoint.
|
|
274
|
+
// If reflectBaseUrl points to a remote Ollama and it's down (MBP offline),
|
|
275
|
+
// skip reflection entirely instead of waiting through 3 retries (~3.5 min).
|
|
276
|
+
// Scoring + linking + decay still run (they use the local model or don't need LLM).
|
|
277
|
+
let skipReflection = false;
|
|
278
|
+
if (llmConfig.reflectBaseUrl && (llmConfig.reflectProvider ?? llmConfig.provider) === "ollama") {
|
|
279
|
+
const reflectModel = llmConfig.reflectModel ?? llmConfig.model;
|
|
280
|
+
const health = await (0, llm_js_1.probeOllamaModel)(llmConfig.reflectBaseUrl, reflectModel);
|
|
281
|
+
if (!health.ok) {
|
|
282
|
+
const reason = health.reason === "unreachable"
|
|
283
|
+
? `reflect endpoint unreachable (${llmConfig.reflectBaseUrl})`
|
|
284
|
+
: `reflect model not loaded (${reflectModel} missing on ${llmConfig.reflectBaseUrl})`;
|
|
285
|
+
console.warn(`[hicortex] ${reason} — skipping reflection, scoring + linking will still run`);
|
|
286
|
+
skipReflection = true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
266
289
|
console.log(`[hicortex] Running consolidation...`);
|
|
267
|
-
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun);
|
|
290
|
+
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, skipReflection);
|
|
268
291
|
console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
|
|
269
292
|
(report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
|
|
270
293
|
}
|
|
271
|
-
// Step 4: Inject lessons into CLAUDE.md
|
|
294
|
+
// Step 4: Inject lessons into the target file (CLAUDE.md or EXPERIENCE.md
|
|
295
|
+
// or custom path — configurable via lessonTarget in config.json)
|
|
272
296
|
if (!dryRun) {
|
|
273
|
-
const
|
|
274
|
-
|
|
297
|
+
const lessonTarget = savedConfig?.lessonTarget;
|
|
298
|
+
const injection = await (0, claude_md_js_1.injectLessons)(db, {
|
|
299
|
+
claudeMdPath: lessonTarget,
|
|
300
|
+
stateDir,
|
|
301
|
+
});
|
|
302
|
+
console.log(`[hicortex] Lessons updated: ${injection.lessonsCount} lessons at ${injection.path}`);
|
|
275
303
|
}
|
|
276
304
|
// Step 5: Update last-run timestamp
|
|
277
305
|
// CRITICAL: only advance lastRun if every session was processed without
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi agent transcript reader.
|
|
3
|
+
*
|
|
4
|
+
* Reads session .jsonl files from ~/.pi/agent/sessions/ in the Pi coding
|
|
5
|
+
* agent's format. Returns the same TranscriptBatch shape as the CC reader
|
|
6
|
+
* (transcript-reader.ts) so the downstream distillation pipeline is
|
|
7
|
+
* format-agnostic.
|
|
8
|
+
*
|
|
9
|
+
* Pi JSONL format (version 3):
|
|
10
|
+
* session — session header: {id, cwd, timestamp, version}
|
|
11
|
+
* model_change — provider + model switch (skip for distillation)
|
|
12
|
+
* thinking_level_change — thinking mode (skip)
|
|
13
|
+
* message — user/assistant/toolResult conversation entries
|
|
14
|
+
* custom — extension events (skip)
|
|
15
|
+
* custom_message — extension messages (skip)
|
|
16
|
+
*
|
|
17
|
+
* Directory layout:
|
|
18
|
+
* ~/.pi/agent/sessions/
|
|
19
|
+
* --home-agents-Agents-raider--/
|
|
20
|
+
* 2026-04-10T18-37-44-615Z_<uuid>.jsonl
|
|
21
|
+
* 2026-04-11T07-51-28-282Z_<uuid>.jsonl
|
|
22
|
+
* --home-agents-Development-MAIC--/
|
|
23
|
+
* ...
|
|
24
|
+
*
|
|
25
|
+
* The encoded-cwd uses double-dash separators: /home/agents/Agents/raider
|
|
26
|
+
* becomes --home-agents-Agents-raider--. The session header's `cwd` field
|
|
27
|
+
* is the canonical path; the directory name is a filesystem-safe encoding.
|
|
28
|
+
*/
|
|
29
|
+
export interface TranscriptBatch {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
projectName: string;
|
|
32
|
+
date: string;
|
|
33
|
+
entries: unknown[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read Pi session transcripts modified after `since`.
|
|
37
|
+
*
|
|
38
|
+
* Scans the Pi sessions directory for .jsonl files, filters by mtime,
|
|
39
|
+
* parses each into a TranscriptBatch.
|
|
40
|
+
*
|
|
41
|
+
* @param since Only return sessions with mtime > this date
|
|
42
|
+
* @param sessionsDir Override the session directory (default: ~/.pi/agent/sessions/)
|
|
43
|
+
*/
|
|
44
|
+
export declare function readPiTranscripts(since: Date, sessionsDir?: string): TranscriptBatch[];
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pi agent transcript reader.
|
|
4
|
+
*
|
|
5
|
+
* Reads session .jsonl files from ~/.pi/agent/sessions/ in the Pi coding
|
|
6
|
+
* agent's format. Returns the same TranscriptBatch shape as the CC reader
|
|
7
|
+
* (transcript-reader.ts) so the downstream distillation pipeline is
|
|
8
|
+
* format-agnostic.
|
|
9
|
+
*
|
|
10
|
+
* Pi JSONL format (version 3):
|
|
11
|
+
* session — session header: {id, cwd, timestamp, version}
|
|
12
|
+
* model_change — provider + model switch (skip for distillation)
|
|
13
|
+
* thinking_level_change — thinking mode (skip)
|
|
14
|
+
* message — user/assistant/toolResult conversation entries
|
|
15
|
+
* custom — extension events (skip)
|
|
16
|
+
* custom_message — extension messages (skip)
|
|
17
|
+
*
|
|
18
|
+
* Directory layout:
|
|
19
|
+
* ~/.pi/agent/sessions/
|
|
20
|
+
* --home-agents-Agents-raider--/
|
|
21
|
+
* 2026-04-10T18-37-44-615Z_<uuid>.jsonl
|
|
22
|
+
* 2026-04-11T07-51-28-282Z_<uuid>.jsonl
|
|
23
|
+
* --home-agents-Development-MAIC--/
|
|
24
|
+
* ...
|
|
25
|
+
*
|
|
26
|
+
* The encoded-cwd uses double-dash separators: /home/agents/Agents/raider
|
|
27
|
+
* becomes --home-agents-Agents-raider--. The session header's `cwd` field
|
|
28
|
+
* is the canonical path; the directory name is a filesystem-safe encoding.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.readPiTranscripts = readPiTranscripts;
|
|
32
|
+
const node_fs_1 = require("node:fs");
|
|
33
|
+
const node_path_1 = require("node:path");
|
|
34
|
+
const node_os_1 = require("node:os");
|
|
35
|
+
const DEFAULT_PI_SESSIONS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "agent", "sessions");
|
|
36
|
+
/**
|
|
37
|
+
* Read Pi session transcripts modified after `since`.
|
|
38
|
+
*
|
|
39
|
+
* Scans the Pi sessions directory for .jsonl files, filters by mtime,
|
|
40
|
+
* parses each into a TranscriptBatch.
|
|
41
|
+
*
|
|
42
|
+
* @param since Only return sessions with mtime > this date
|
|
43
|
+
* @param sessionsDir Override the session directory (default: ~/.pi/agent/sessions/)
|
|
44
|
+
*/
|
|
45
|
+
function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
46
|
+
const batches = [];
|
|
47
|
+
let projectDirs;
|
|
48
|
+
try {
|
|
49
|
+
projectDirs = (0, node_fs_1.readdirSync)(sessionsDir);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Directory doesn't exist — no Pi sessions. Not an error.
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
for (const projectDir of projectDirs) {
|
|
56
|
+
const projectPath = (0, node_path_1.join)(sessionsDir, projectDir);
|
|
57
|
+
let files;
|
|
58
|
+
try {
|
|
59
|
+
const stat = (0, node_fs_1.statSync)(projectPath);
|
|
60
|
+
if (!stat.isDirectory())
|
|
61
|
+
continue;
|
|
62
|
+
files = (0, node_fs_1.readdirSync)(projectPath).filter((f) => f.endsWith(".jsonl"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const filePath = (0, node_path_1.join)(projectPath, file);
|
|
69
|
+
// Filter by modification time
|
|
70
|
+
try {
|
|
71
|
+
const stat = (0, node_fs_1.statSync)(filePath);
|
|
72
|
+
if (stat.mtime <= since)
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Parse the JSONL file
|
|
79
|
+
try {
|
|
80
|
+
const raw = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
81
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
82
|
+
const entries = [];
|
|
83
|
+
let sessionId = "";
|
|
84
|
+
let sessionCwd = "";
|
|
85
|
+
let sessionDate = "";
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
try {
|
|
88
|
+
const entry = JSON.parse(line);
|
|
89
|
+
entries.push(entry);
|
|
90
|
+
// Extract metadata from the session header
|
|
91
|
+
if (entry.type === "session") {
|
|
92
|
+
sessionId = entry.id ?? "";
|
|
93
|
+
sessionCwd = entry.cwd ?? "";
|
|
94
|
+
// Date from the session timestamp or filename
|
|
95
|
+
sessionDate =
|
|
96
|
+
entry.timestamp?.slice(0, 10) ??
|
|
97
|
+
extractDateFromFilename(file) ??
|
|
98
|
+
"";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Skip malformed lines
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Derive project name from the cwd or directory name
|
|
106
|
+
const projectName = deriveProjectName(sessionCwd, projectDir);
|
|
107
|
+
// Use filename UUID as fallback session ID
|
|
108
|
+
if (!sessionId) {
|
|
109
|
+
sessionId = extractUuidFromFilename(file) ?? file;
|
|
110
|
+
}
|
|
111
|
+
if (!sessionDate) {
|
|
112
|
+
sessionDate = extractDateFromFilename(file) ?? "";
|
|
113
|
+
}
|
|
114
|
+
if (entries.length > 0) {
|
|
115
|
+
batches.push({
|
|
116
|
+
sessionId,
|
|
117
|
+
projectName,
|
|
118
|
+
date: sessionDate,
|
|
119
|
+
entries,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// File read or parse failed — skip
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return batches;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract the last path segment from a cwd as the project name.
|
|
132
|
+
* /home/agents/Agents/raider → "raider"
|
|
133
|
+
* Falls back to decoding the directory name if cwd is empty.
|
|
134
|
+
*/
|
|
135
|
+
function deriveProjectName(cwd, encodedDir) {
|
|
136
|
+
if (cwd) {
|
|
137
|
+
const segments = cwd.split("/").filter(Boolean);
|
|
138
|
+
return segments[segments.length - 1] ?? "unknown";
|
|
139
|
+
}
|
|
140
|
+
// Decode the Pi directory encoding: --home-agents-Agents-raider-- → raider
|
|
141
|
+
const decoded = encodedDir.replace(/^--/, "").replace(/--$/, "").split("-");
|
|
142
|
+
return decoded[decoded.length - 1] ?? "unknown";
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extract the date (YYYY-MM-DD) from a Pi session filename.
|
|
146
|
+
* Format: 2026-04-10T18-37-44-615Z_<uuid>.jsonl → "2026-04-10"
|
|
147
|
+
*/
|
|
148
|
+
function extractDateFromFilename(filename) {
|
|
149
|
+
const match = filename.match(/^(\d{4}-\d{2}-\d{2})T/);
|
|
150
|
+
return match ? match[1] : null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Extract the UUID from a Pi session filename.
|
|
154
|
+
* Format: 2026-04-10T18-37-44-615Z_f4227d47-e54f-4977-a50c-4de7f6d1fa21.jsonl
|
|
155
|
+
*/
|
|
156
|
+
function extractUuidFromFilename(filename) {
|
|
157
|
+
const match = filename.match(/_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
|
|
158
|
+
return match ? match[1] : null;
|
|
159
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pro extension loader.
|
|
3
|
+
*
|
|
4
|
+
* Called from features.ts at boot when a valid paid license is detected.
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* 1. Check ~/.hicortex/pro/installed.json for the currently-installed
|
|
7
|
+
* Pro version (if any).
|
|
8
|
+
* 2. Fetch GET /api/pro/meta from hicortex.gamaze.com to discover the
|
|
9
|
+
* latest available version for the caller's license tier.
|
|
10
|
+
* 3. If the installed version is older (or missing), download the
|
|
11
|
+
* tarball, verify the sha256 sidecar, and extract to ~/.hicortex/pro/.
|
|
12
|
+
* 4. Dynamic-import ~/.hicortex/pro/package/index.js and call activate()
|
|
13
|
+
* on its default export with a ProActivationContext.
|
|
14
|
+
*
|
|
15
|
+
* Failure modes (all soft — OSS host keeps running with defaults):
|
|
16
|
+
* - Network to /api/pro/meta fails → use whatever is already installed
|
|
17
|
+
* - No cached Pro and network fails → Pro not activated, OSS defaults apply
|
|
18
|
+
* - Downloaded tarball fails sha256 → abort download, keep old version
|
|
19
|
+
* - import() of activated module throws → log warning, keep defaults
|
|
20
|
+
*
|
|
21
|
+
* The loader is strictly best-effort. It must NEVER crash the OSS host.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Entry point called from features.ts at boot.
|
|
25
|
+
*
|
|
26
|
+
* @param licenseKey The Pro license key (hctx-...) from config
|
|
27
|
+
* @param stateDir Usually ~/.hicortex/
|
|
28
|
+
* @param hostVersion The version of the OSS host (from package.json) — passed
|
|
29
|
+
* to the Pro activate() for compatibility gating
|
|
30
|
+
* @param serverUrl Override for the Pro meta/download endpoint (defaults
|
|
31
|
+
* to https://hicortex.gamaze.com). Useful for testing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadPro(licenseKey: string, stateDir: string, hostVersion: string, serverUrl?: string): Promise<void>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pro extension loader.
|
|
4
|
+
*
|
|
5
|
+
* Called from features.ts at boot when a valid paid license is detected.
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* 1. Check ~/.hicortex/pro/installed.json for the currently-installed
|
|
8
|
+
* Pro version (if any).
|
|
9
|
+
* 2. Fetch GET /api/pro/meta from hicortex.gamaze.com to discover the
|
|
10
|
+
* latest available version for the caller's license tier.
|
|
11
|
+
* 3. If the installed version is older (or missing), download the
|
|
12
|
+
* tarball, verify the sha256 sidecar, and extract to ~/.hicortex/pro/.
|
|
13
|
+
* 4. Dynamic-import ~/.hicortex/pro/package/index.js and call activate()
|
|
14
|
+
* on its default export with a ProActivationContext.
|
|
15
|
+
*
|
|
16
|
+
* Failure modes (all soft — OSS host keeps running with defaults):
|
|
17
|
+
* - Network to /api/pro/meta fails → use whatever is already installed
|
|
18
|
+
* - No cached Pro and network fails → Pro not activated, OSS defaults apply
|
|
19
|
+
* - Downloaded tarball fails sha256 → abort download, keep old version
|
|
20
|
+
* - import() of activated module throws → log warning, keep defaults
|
|
21
|
+
*
|
|
22
|
+
* The loader is strictly best-effort. It must NEVER crash the OSS host.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.loadPro = loadPro;
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
const node_url_1 = require("node:url");
|
|
30
|
+
const node_child_process_1 = require("node:child_process");
|
|
31
|
+
const extensions_js_1 = require("./extensions.js");
|
|
32
|
+
const VALIDATE_URL = "https://hicortex.gamaze.com";
|
|
33
|
+
/**
|
|
34
|
+
* Entry point called from features.ts at boot.
|
|
35
|
+
*
|
|
36
|
+
* @param licenseKey The Pro license key (hctx-...) from config
|
|
37
|
+
* @param stateDir Usually ~/.hicortex/
|
|
38
|
+
* @param hostVersion The version of the OSS host (from package.json) — passed
|
|
39
|
+
* to the Pro activate() for compatibility gating
|
|
40
|
+
* @param serverUrl Override for the Pro meta/download endpoint (defaults
|
|
41
|
+
* to https://hicortex.gamaze.com). Useful for testing.
|
|
42
|
+
*/
|
|
43
|
+
async function loadPro(licenseKey, stateDir, hostVersion, serverUrl = VALIDATE_URL) {
|
|
44
|
+
const proRoot = (0, node_path_1.join)(stateDir, "pro");
|
|
45
|
+
try {
|
|
46
|
+
(0, node_fs_1.mkdirSync)(proRoot, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// If we can't even create the dir, there's nothing to load
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Step 1: read installed state (if any)
|
|
53
|
+
const installedStatePath = (0, node_path_1.join)(proRoot, "installed.json");
|
|
54
|
+
const installed = readInstalledState(installedStatePath);
|
|
55
|
+
// Step 2: try to fetch the latest meta. Network failure is soft.
|
|
56
|
+
let meta = null;
|
|
57
|
+
try {
|
|
58
|
+
meta = await fetchProMeta(serverUrl, licenseKey);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
62
|
+
console.warn(`[hicortex][pro] Could not fetch Pro metadata (${msg}) — using cached version if present`);
|
|
63
|
+
}
|
|
64
|
+
// Step 3: if meta is newer than installed, download and extract
|
|
65
|
+
if (meta && (!installed || installed.version !== meta.version)) {
|
|
66
|
+
try {
|
|
67
|
+
await downloadAndExtract(serverUrl, licenseKey, meta, proRoot);
|
|
68
|
+
writeInstalledState(installedStatePath, {
|
|
69
|
+
version: meta.version,
|
|
70
|
+
installedAt: new Date().toISOString(),
|
|
71
|
+
sha256: meta.sha256,
|
|
72
|
+
});
|
|
73
|
+
console.log(`[hicortex][pro] Installed Pro v${meta.version}`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
77
|
+
console.warn(`[hicortex][pro] Pro download failed: ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Step 4: activate whatever is installed (if anything)
|
|
81
|
+
const indexPath = (0, node_path_1.join)(proRoot, "package", "index.js");
|
|
82
|
+
if (!(0, node_fs_1.existsSync)(indexPath)) {
|
|
83
|
+
// No Pro installed and meta fetch couldn't install one
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const mod = await import((0, node_url_1.pathToFileURL)(indexPath).href);
|
|
88
|
+
const pkg = (mod.default ?? mod);
|
|
89
|
+
if (typeof pkg?.activate !== "function") {
|
|
90
|
+
console.warn(`[hicortex][pro] Pro package has no activate() function`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const ctx = (0, extensions_js_1.createProActivationContext)(hostVersion);
|
|
94
|
+
await pkg.activate(ctx);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
console.warn(`[hicortex][pro] Pro activation failed: ${msg}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Implementation helpers
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
function readInstalledState(path) {
|
|
105
|
+
try {
|
|
106
|
+
const raw = (0, node_fs_1.readFileSync)(path, "utf-8");
|
|
107
|
+
return JSON.parse(raw);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function writeInstalledState(path, state) {
|
|
114
|
+
try {
|
|
115
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
116
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(state, null, 2));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Non-fatal — we'll just re-download next boot
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function fetchProMeta(serverUrl, licenseKey) {
|
|
123
|
+
const url = `${serverUrl.replace(/\/$/, "")}/api/pro/meta`;
|
|
124
|
+
const resp = await fetch(url, {
|
|
125
|
+
headers: { Authorization: `Bearer ${licenseKey}` },
|
|
126
|
+
signal: AbortSignal.timeout(10_000),
|
|
127
|
+
});
|
|
128
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
129
|
+
throw new Error(`Pro access denied (HTTP ${resp.status}) — check license key`);
|
|
130
|
+
}
|
|
131
|
+
if (resp.status === 404) {
|
|
132
|
+
throw new Error("No Pro release available yet");
|
|
133
|
+
}
|
|
134
|
+
if (!resp.ok) {
|
|
135
|
+
throw new Error(`HTTP ${resp.status}`);
|
|
136
|
+
}
|
|
137
|
+
const data = (await resp.json());
|
|
138
|
+
if (!data.version || !data.url) {
|
|
139
|
+
throw new Error("Malformed /api/pro/meta response");
|
|
140
|
+
}
|
|
141
|
+
return data;
|
|
142
|
+
}
|
|
143
|
+
async function downloadAndExtract(serverUrl, licenseKey, meta, proRoot) {
|
|
144
|
+
const downloadUrl = `${serverUrl.replace(/\/$/, "")}${meta.url.startsWith("/") ? "" : "/"}${meta.url}`;
|
|
145
|
+
const resp = await fetch(downloadUrl, {
|
|
146
|
+
headers: { Authorization: `Bearer ${licenseKey}` },
|
|
147
|
+
signal: AbortSignal.timeout(60_000),
|
|
148
|
+
});
|
|
149
|
+
if (!resp.ok) {
|
|
150
|
+
throw new Error(`Download failed with HTTP ${resp.status}`);
|
|
151
|
+
}
|
|
152
|
+
// Stream to a temp file so we can verify the hash before extracting
|
|
153
|
+
const tmpPath = (0, node_path_1.join)(proRoot, `.pro-download-${Date.now()}.tgz`);
|
|
154
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
155
|
+
(0, node_fs_1.writeFileSync)(tmpPath, buf);
|
|
156
|
+
// Verify sha256 if the server provided one
|
|
157
|
+
if (meta.sha256) {
|
|
158
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update(buf).digest("hex");
|
|
159
|
+
if (hash !== meta.sha256) {
|
|
160
|
+
try {
|
|
161
|
+
(0, node_fs_1.rmSync)(tmpPath);
|
|
162
|
+
}
|
|
163
|
+
catch { /* non-fatal */ }
|
|
164
|
+
throw new Error(`sha256 mismatch: expected ${meta.sha256}, got ${hash}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Extract to proRoot (overwrites existing package/ directory)
|
|
168
|
+
const existingPackageDir = (0, node_path_1.join)(proRoot, "package");
|
|
169
|
+
if ((0, node_fs_1.existsSync)(existingPackageDir)) {
|
|
170
|
+
try {
|
|
171
|
+
(0, node_fs_1.rmSync)(existingPackageDir, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
catch { /* non-fatal */ }
|
|
174
|
+
}
|
|
175
|
+
// Use the system tar to extract (cross-platform, no new dependency)
|
|
176
|
+
const result = (0, node_child_process_1.spawnSync)("tar", ["-xzf", tmpPath, "-C", proRoot], {
|
|
177
|
+
encoding: "utf-8",
|
|
178
|
+
});
|
|
179
|
+
if (result.status !== 0) {
|
|
180
|
+
throw new Error(`tar extraction failed: ${result.stderr || "unknown error"}`);
|
|
181
|
+
}
|
|
182
|
+
// Clean up the temp tarball
|
|
183
|
+
try {
|
|
184
|
+
(0, node_fs_1.rmSync)(tmpPath);
|
|
185
|
+
}
|
|
186
|
+
catch { /* non-fatal */ }
|
|
187
|
+
}
|
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
|
+
"version": "0.5.1",
|
|
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.
|
|
3
|
+
"version": "0.5.1",
|
|
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": {
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
52
52
|
"url": "https://github.com/gamaze-labs/hicortex.git",
|
|
53
|
-
"directory": "packages/
|
|
53
|
+
"directory": "packages/hicortex"
|
|
54
54
|
},
|
|
55
55
|
"bugs": {
|
|
56
56
|
"url": "https://github.com/gamaze-labs/hicortex/issues"
|
|
57
57
|
},
|
|
58
|
-
"author": "
|
|
58
|
+
"author": "Aironic Ventures Ltd.",
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@huggingface/transformers": "^3.0.0",
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.28.0",
|