@gamaze/hicortex 0.13.1 → 0.13.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/capture-cursors.d.ts +73 -0
- package/dist/capture-cursors.js +133 -0
- package/dist/capture.d.ts +124 -0
- package/dist/capture.js +386 -0
- package/dist/cli.js +13 -1
- package/dist/consolidate.d.ts +1 -1
- package/dist/consolidate.js +1 -1
- package/dist/context-cli.js +1 -1
- package/dist/domain-classify.d.ts +3 -3
- package/dist/domain-classify.js +3 -3
- package/dist/hermes-transcript-reader.d.ts +7 -3
- package/dist/hermes-transcript-reader.js +42 -5
- package/dist/lessons-context.d.ts +1 -1
- package/dist/lessons-context.js +2 -2
- package/dist/mcp-server.js +46 -16
- package/dist/nightly.d.ts +2 -0
- package/dist/nightly.js +224 -176
- package/dist/oc-transcript-reader.d.ts +3 -2
- package/dist/oc-transcript-reader.js +5 -3
- package/dist/pi-transcript-reader.d.ts +8 -11
- package/dist/pi-transcript-reader.js +41 -13
- package/dist/transcript-reader.d.ts +23 -2
- package/dist/transcript-reader.js +49 -16
- package/domains.example.json +53 -13
- package/hermes-plugin/hicortex/provider.py +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -16,23 +16,18 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Directory layout:
|
|
18
18
|
* ~/.pi/agent/sessions/
|
|
19
|
-
* --home-
|
|
19
|
+
* --home-alice-projects-myagent--/
|
|
20
20
|
* 2026-04-10T18-37-44-615Z_<uuid>.jsonl
|
|
21
21
|
* 2026-04-11T07-51-28-282Z_<uuid>.jsonl
|
|
22
22
|
* --home-agents-Development-MAIC--/
|
|
23
23
|
* ...
|
|
24
24
|
*
|
|
25
|
-
* The encoded-cwd uses double-dash separators: /home/
|
|
26
|
-
* becomes --home-
|
|
25
|
+
* The encoded-cwd uses double-dash separators: /home/alice/projects/myagent
|
|
26
|
+
* becomes --home-alice-projects-myagent--. The session header's `cwd` field
|
|
27
27
|
* is the canonical path; the directory name is a filesystem-safe encoding.
|
|
28
28
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
projectName: string;
|
|
32
|
-
date: string;
|
|
33
|
-
entries: unknown[];
|
|
34
|
-
sourceAgent?: string;
|
|
35
|
-
}
|
|
29
|
+
import type { TranscriptBatch, CursorMap } from "./transcript-reader.js";
|
|
30
|
+
export type { TranscriptBatch, CursorMap };
|
|
36
31
|
/**
|
|
37
32
|
* Read Pi session transcripts modified after `since`.
|
|
38
33
|
*
|
|
@@ -41,5 +36,7 @@ export interface TranscriptBatch {
|
|
|
41
36
|
*
|
|
42
37
|
* @param since Only return sessions with mtime > this date
|
|
43
38
|
* @param sessionsDir Override the session directory (default: ~/.pi/agent/sessions/)
|
|
39
|
+
* @param cursors Per-session capture cursors (#189); default empty = whole file
|
|
40
|
+
* @param keyPrefix Cursor-key namespace ("pi" here; OC passes "oc:<agentId>")
|
|
44
41
|
*/
|
|
45
|
-
export declare function readPiTranscripts(since: Date, sessionsDir?: string): TranscriptBatch[];
|
|
42
|
+
export declare function readPiTranscripts(since: Date, sessionsDir?: string, cursors?: CursorMap, keyPrefix?: string): TranscriptBatch[];
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Directory layout:
|
|
19
19
|
* ~/.pi/agent/sessions/
|
|
20
|
-
* --home-
|
|
20
|
+
* --home-alice-projects-myagent--/
|
|
21
21
|
* 2026-04-10T18-37-44-615Z_<uuid>.jsonl
|
|
22
22
|
* 2026-04-11T07-51-28-282Z_<uuid>.jsonl
|
|
23
23
|
* --home-agents-Development-MAIC--/
|
|
24
24
|
* ...
|
|
25
25
|
*
|
|
26
|
-
* The encoded-cwd uses double-dash separators: /home/
|
|
27
|
-
* becomes --home-
|
|
26
|
+
* The encoded-cwd uses double-dash separators: /home/alice/projects/myagent
|
|
27
|
+
* becomes --home-alice-projects-myagent--. The session header's `cwd` field
|
|
28
28
|
* is the canonical path; the directory name is a filesystem-safe encoding.
|
|
29
29
|
*/
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -41,8 +41,10 @@ const DEFAULT_PI_SESSIONS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(),
|
|
|
41
41
|
*
|
|
42
42
|
* @param since Only return sessions with mtime > this date
|
|
43
43
|
* @param sessionsDir Override the session directory (default: ~/.pi/agent/sessions/)
|
|
44
|
+
* @param cursors Per-session capture cursors (#189); default empty = whole file
|
|
45
|
+
* @param keyPrefix Cursor-key namespace ("pi" here; OC passes "oc:<agentId>")
|
|
44
46
|
*/
|
|
45
|
-
function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
47
|
+
function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR, cursors = {}, keyPrefix = "pi") {
|
|
46
48
|
const batches = [];
|
|
47
49
|
let projectDirs;
|
|
48
50
|
try {
|
|
@@ -80,6 +82,7 @@ function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
|
80
82
|
const raw = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
81
83
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
82
84
|
const entries = [];
|
|
85
|
+
const timestamps = [];
|
|
83
86
|
let sessionId = "";
|
|
84
87
|
let sessionCwd = "";
|
|
85
88
|
let sessionDate = "";
|
|
@@ -87,6 +90,7 @@ function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
|
87
90
|
try {
|
|
88
91
|
const entry = JSON.parse(line);
|
|
89
92
|
entries.push(entry);
|
|
93
|
+
timestamps.push(typeof entry.timestamp === "string" ? entry.timestamp : "");
|
|
90
94
|
// Extract metadata from the session header
|
|
91
95
|
if (entry.type === "session") {
|
|
92
96
|
sessionId = entry.id ?? "";
|
|
@@ -111,14 +115,38 @@ function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
|
111
115
|
if (!sessionDate) {
|
|
112
116
|
sessionDate = extractDateFromFilename(file) ?? "";
|
|
113
117
|
}
|
|
114
|
-
if (entries.length
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (entries.length === 0)
|
|
119
|
+
continue;
|
|
120
|
+
// Incremental slice (#189): append-only JSONL v3, same discipline as CC.
|
|
121
|
+
const cursorKey = `${keyPrefix}:${sessionId}`;
|
|
122
|
+
const pos = cursors[cursorKey] ?? { cursor: 0, gen: 0 };
|
|
123
|
+
let start = pos.cursor;
|
|
124
|
+
let gen = pos.gen;
|
|
125
|
+
if (start > entries.length) {
|
|
126
|
+
// shrink guard (truncation/rotation) — reset + bump generation (fix 8)
|
|
127
|
+
start = 0;
|
|
128
|
+
gen = pos.gen + 1;
|
|
129
|
+
}
|
|
130
|
+
const delta = entries.slice(start);
|
|
131
|
+
if (delta.length === 0)
|
|
132
|
+
continue; // cursor already covers the file
|
|
133
|
+
const entryCursors = delta.map((_, i) => start + i + 1);
|
|
134
|
+
// Prefer the last timestamped entry in the delta for per-night dating.
|
|
135
|
+
let deltaDate = "";
|
|
136
|
+
for (let i = start; i < entries.length; i++) {
|
|
137
|
+
if (timestamps[i])
|
|
138
|
+
deltaDate = timestamps[i].slice(0, 10);
|
|
121
139
|
}
|
|
140
|
+
batches.push({
|
|
141
|
+
sessionId,
|
|
142
|
+
projectName,
|
|
143
|
+
date: deltaDate || sessionDate || new Date().toISOString().slice(0, 10),
|
|
144
|
+
entries: delta,
|
|
145
|
+
cursorKey,
|
|
146
|
+
startCursor: start,
|
|
147
|
+
generation: gen,
|
|
148
|
+
entryCursors,
|
|
149
|
+
});
|
|
122
150
|
}
|
|
123
151
|
catch {
|
|
124
152
|
// File read or parse failed — skip
|
|
@@ -129,7 +157,7 @@ function readPiTranscripts(since, sessionsDir = DEFAULT_PI_SESSIONS_DIR) {
|
|
|
129
157
|
}
|
|
130
158
|
/**
|
|
131
159
|
* Extract the last path segment from a cwd as the project name.
|
|
132
|
-
* /home/
|
|
160
|
+
* /home/alice/projects/myagent → "myagent"
|
|
133
161
|
* Falls back to decoding the directory name if cwd is empty.
|
|
134
162
|
*/
|
|
135
163
|
function deriveProjectName(cwd, encodedDir) {
|
|
@@ -137,7 +165,7 @@ function deriveProjectName(cwd, encodedDir) {
|
|
|
137
165
|
const segments = cwd.split("/").filter(Boolean);
|
|
138
166
|
return segments[segments.length - 1] ?? "unknown";
|
|
139
167
|
}
|
|
140
|
-
// Decode the Pi directory encoding: --home-
|
|
168
|
+
// Decode the Pi directory encoding: --home-alice-projects-myagent-- → myagent
|
|
141
169
|
const decoded = encodedDir.replace(/^--/, "").replace(/--$/, "").split("-");
|
|
142
170
|
return decoded[decoded.length - 1] ?? "unknown";
|
|
143
171
|
}
|
|
@@ -7,17 +7,38 @@
|
|
|
7
7
|
* The reader scans for new sessions since the last nightly run
|
|
8
8
|
* and feeds them to the existing distiller pipeline.
|
|
9
9
|
*/
|
|
10
|
+
import type { CursorMap } from "./capture-cursors.js";
|
|
11
|
+
export type { CursorMap };
|
|
10
12
|
export interface TranscriptBatch {
|
|
11
13
|
sessionId: string;
|
|
12
14
|
projectName: string;
|
|
13
15
|
date: string;
|
|
14
16
|
entries: unknown[];
|
|
15
17
|
/**
|
|
16
|
-
* Optional source-agent label (e.g. "hermes/
|
|
18
|
+
* Optional source-agent label (e.g. "hermes/alice"). When set, the nightly
|
|
17
19
|
* pipeline uses it verbatim for provenance instead of the default
|
|
18
20
|
* `claude-code/<project>`. Lets per-harness readers stamp their own origin.
|
|
19
21
|
*/
|
|
20
22
|
sourceAgent?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Per-session cursor key (`<prefix>:<sessionId>`) — the capture-cursors.json
|
|
25
|
+
* key whose value gates and advances this session's incremental capture (#189).
|
|
26
|
+
*/
|
|
27
|
+
cursorKey: string;
|
|
28
|
+
/** Cursor value the delta starts from (entries already captured before this run). */
|
|
29
|
+
startCursor: number;
|
|
30
|
+
/**
|
|
31
|
+
* Shrink-guard generation for this session — woven into segment ids so
|
|
32
|
+
* post-reset segments can't collide with pre-reset ids on the content-blind
|
|
33
|
+
* server dedup. Advanced back to the store with the cursor.
|
|
34
|
+
*/
|
|
35
|
+
generation: number;
|
|
36
|
+
/**
|
|
37
|
+
* End-cursor value for each delta entry (length === entries.length). The
|
|
38
|
+
* packer uses these to land segment boundaries on exact entry boundaries.
|
|
39
|
+
* JSONL: startCursor + i + 1. Hermes: the row's messages.id.
|
|
40
|
+
*/
|
|
41
|
+
entryCursors: number[];
|
|
21
42
|
}
|
|
22
43
|
/**
|
|
23
44
|
* Cheap pre-filter: skip CC session FILES with fewer than this many raw JSONL
|
|
@@ -36,4 +57,4 @@ export declare const MIN_TRANSCRIPT_ENTRIES = 4;
|
|
|
36
57
|
* Read all CC transcripts modified since `since`.
|
|
37
58
|
* Returns one batch per session file.
|
|
38
59
|
*/
|
|
39
|
-
export declare function readCcTranscripts(since: Date, projectsDir?: string): TranscriptBatch[];
|
|
60
|
+
export declare function readCcTranscripts(since: Date, projectsDir?: string, cursors?: CursorMap): TranscriptBatch[];
|
|
@@ -32,7 +32,7 @@ const CC_PROJECTS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude
|
|
|
32
32
|
* Read all CC transcripts modified since `since`.
|
|
33
33
|
* Returns one batch per session file.
|
|
34
34
|
*/
|
|
35
|
-
function readCcTranscripts(since, projectsDir = CC_PROJECTS_DIR) {
|
|
35
|
+
function readCcTranscripts(since, projectsDir = CC_PROJECTS_DIR, cursors = {}) {
|
|
36
36
|
const batches = [];
|
|
37
37
|
let projectDirs;
|
|
38
38
|
try {
|
|
@@ -74,7 +74,10 @@ function readCcTranscripts(since, projectsDir = CC_PROJECTS_DIR) {
|
|
|
74
74
|
// Skip files not modified since last run
|
|
75
75
|
if (fileStat.mtime <= since)
|
|
76
76
|
continue;
|
|
77
|
-
const
|
|
77
|
+
const sessionId = (0, node_path_1.basename)(filePath, ".jsonl");
|
|
78
|
+
const key = `cc:${sessionId}`;
|
|
79
|
+
const pos = cursors[key] ?? { cursor: 0, gen: 0 };
|
|
80
|
+
const batch = parseTranscriptFile(filePath, projectName, key, pos.cursor, pos.gen);
|
|
78
81
|
if (batch) {
|
|
79
82
|
batches.push(batch);
|
|
80
83
|
}
|
|
@@ -83,10 +86,15 @@ function readCcTranscripts(since, projectsDir = CC_PROJECTS_DIR) {
|
|
|
83
86
|
return batches;
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
|
-
* Parse a single .jsonl transcript file into a batch.
|
|
87
|
-
*
|
|
89
|
+
* Parse a single .jsonl transcript file into a delta batch.
|
|
90
|
+
*
|
|
91
|
+
* Returns null if the file has too few meaningful entries (whole-file gate) or
|
|
92
|
+
* the cursor already covers everything (nothing new since last capture, #189).
|
|
93
|
+
*
|
|
94
|
+
* @param cursorKey capture-cursors.json key for this session
|
|
95
|
+
* @param startCursor entries already captured (delta = entries.slice(startCursor))
|
|
88
96
|
*/
|
|
89
|
-
function parseTranscriptFile(filePath, projectName) {
|
|
97
|
+
function parseTranscriptFile(filePath, projectName, cursorKey, startCursor, generation) {
|
|
90
98
|
let raw;
|
|
91
99
|
try {
|
|
92
100
|
raw = (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
@@ -98,40 +106,65 @@ function parseTranscriptFile(filePath, projectName) {
|
|
|
98
106
|
if (lines.length < exports.MIN_TRANSCRIPT_ENTRIES)
|
|
99
107
|
return null; // degenerate/empty file
|
|
100
108
|
const entries = [];
|
|
101
|
-
|
|
109
|
+
const timestamps = [];
|
|
102
110
|
for (const line of lines) {
|
|
103
111
|
try {
|
|
104
112
|
const entry = JSON.parse(line);
|
|
105
113
|
entries.push(entry);
|
|
106
|
-
|
|
107
|
-
lastTimestamp = entry.timestamp;
|
|
108
|
-
}
|
|
114
|
+
timestamps.push(typeof entry.timestamp === "string" ? entry.timestamp : "");
|
|
109
115
|
}
|
|
110
116
|
catch {
|
|
111
|
-
// Skip malformed lines
|
|
117
|
+
// Skip malformed lines — a permanently-malformed line is skipped
|
|
118
|
+
// identically every run, and a partial trailing write fails JSON.parse
|
|
119
|
+
// now and parses (at the same index) once fully flushed.
|
|
112
120
|
}
|
|
113
121
|
}
|
|
122
|
+
// Whole-file degeneracy gate stays on the full parse, not the delta.
|
|
114
123
|
if (entries.length < exports.MIN_TRANSCRIPT_ENTRIES)
|
|
115
124
|
return null;
|
|
116
|
-
//
|
|
117
|
-
|
|
125
|
+
// Shrink guard: a truncated/rotated file with fewer entries than the stored
|
|
126
|
+
// cursor → reset to 0 AND bump the generation. The generation is woven into
|
|
127
|
+
// the segment id downstream so the fresh file's segments can never collide
|
|
128
|
+
// with the pre-reset ids on the server's content-blind dedup (fix 8).
|
|
129
|
+
let start = startCursor;
|
|
130
|
+
let gen = generation;
|
|
131
|
+
if (start > entries.length) {
|
|
132
|
+
start = 0;
|
|
133
|
+
gen = generation + 1;
|
|
134
|
+
}
|
|
135
|
+
const delta = entries.slice(start);
|
|
136
|
+
if (delta.length === 0)
|
|
137
|
+
return null; // cursor already covers the whole file
|
|
138
|
+
// Per-entry end cursors: entry i (0-based in the delta) ends at start+i+1.
|
|
139
|
+
const entryCursors = delta.map((_, i) => start + i + 1);
|
|
140
|
+
// Date from the LAST timestamped entry in the delta (per-night created_at for
|
|
141
|
+
// multi-day sessions), falling back to today.
|
|
142
|
+
let lastTimestamp = "";
|
|
143
|
+
for (let i = start; i < entries.length; i++) {
|
|
144
|
+
if (timestamps[i])
|
|
145
|
+
lastTimestamp = timestamps[i];
|
|
146
|
+
}
|
|
118
147
|
return {
|
|
119
|
-
sessionId,
|
|
148
|
+
sessionId: (0, node_path_1.basename)(filePath, ".jsonl"),
|
|
120
149
|
projectName,
|
|
121
150
|
date: lastTimestamp
|
|
122
151
|
? lastTimestamp.slice(0, 10)
|
|
123
152
|
: new Date().toISOString().slice(0, 10),
|
|
124
|
-
entries,
|
|
153
|
+
entries: delta,
|
|
154
|
+
cursorKey,
|
|
155
|
+
startCursor: start,
|
|
156
|
+
generation: gen,
|
|
157
|
+
entryCursors,
|
|
125
158
|
};
|
|
126
159
|
}
|
|
127
160
|
/**
|
|
128
161
|
* Decode CC project directory name to a human-readable project name.
|
|
129
|
-
* CC uses path-based hashing: "-Users-
|
|
162
|
+
* CC uses path-based hashing: "-Users-alice-Development-Tools-hicortex"
|
|
130
163
|
* becomes "hicortex" (last path component).
|
|
131
164
|
*/
|
|
132
165
|
function decodeProjectDirName(dirName) {
|
|
133
166
|
// CC encodes paths by replacing / with -
|
|
134
|
-
// e.g. "-Users-
|
|
167
|
+
// e.g. "-Users-alice-Development-Tools-hicortex"
|
|
135
168
|
const parts = dirName.split("-").filter(Boolean);
|
|
136
169
|
if (parts.length === 0)
|
|
137
170
|
return dirName;
|
package/domains.example.json
CHANGED
|
@@ -9,11 +9,26 @@
|
|
|
9
9
|
"Backfill an existing corpus with: hicortex classify-domains"
|
|
10
10
|
],
|
|
11
11
|
"domains": [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
12
|
+
{
|
|
13
|
+
"name": "Work",
|
|
14
|
+
"description": "Your job and professional life — employer, clients, workstreams"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "Personal",
|
|
18
|
+
"description": "Private life — home, hobbies, everyday matters"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "People",
|
|
22
|
+
"description": "Relationships — family, friends, social life, network"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Health",
|
|
26
|
+
"description": "Fitness, wellbeing, medical"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "Finance",
|
|
30
|
+
"description": "Money — budgeting, spending, investing"
|
|
31
|
+
}
|
|
17
32
|
],
|
|
18
33
|
"_powerUserExample": {
|
|
19
34
|
"_readme": [
|
|
@@ -22,14 +37,39 @@
|
|
|
22
37
|
"`weakPrimaryFloor` (default 0.45) is the minimum embedding similarity for a no-fit memory to earn a weak primary; tune it from your corpus."
|
|
23
38
|
],
|
|
24
39
|
"domains": [
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
{
|
|
41
|
+
"name": "Work",
|
|
42
|
+
"description": "Employer, day job, client projects, workstreams",
|
|
43
|
+
"compartment": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Personal",
|
|
47
|
+
"description": "Private life — home, hobbies, everyday matters"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "People",
|
|
51
|
+
"description": "Relationships — family, friends, social life, network"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "Health",
|
|
55
|
+
"description": "Fitness, wellbeing, medical"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Finance",
|
|
59
|
+
"description": "Money — budgeting, spending, investing"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "Photography",
|
|
63
|
+
"description": "Camera gear, shoots, editing workflow, photo projects"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "Home",
|
|
67
|
+
"description": "House, renovation projects, maintenance, garden"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "Travel",
|
|
71
|
+
"description": "Trips, destinations, bookings, travel plans"
|
|
72
|
+
}
|
|
33
73
|
],
|
|
34
74
|
"weakPrimaryFloor": 0.5
|
|
35
75
|
}
|
|
@@ -33,8 +33,8 @@ _INJECT_CONTENT_CAP = 500
|
|
|
33
33
|
|
|
34
34
|
# Agent ids are joined into a filesystem path server-side, so they share the
|
|
35
35
|
# section-name allowlist. \Z (NOT $) anchors the END OF STRING: Python's $ also
|
|
36
|
-
# matches just before a trailing "\n", so "
|
|
37
|
-
# agent=
|
|
36
|
+
# matches just before a trailing "\n", so "alice\n" would pass and go out as
|
|
37
|
+
# agent=alice%0A → a 400 the fail-soft path silently swallows.
|
|
38
38
|
_AGENT_ID_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*\Z")
|
|
39
39
|
|
|
40
40
|
|
|
@@ -48,7 +48,7 @@ def _sanitize_agent_id(raw: Optional[str]) -> Optional[str]:
|
|
|
48
48
|
EXACTLY so a profile resolves to the SAME id on both harnesses (a mismatch
|
|
49
49
|
would make one honor the persona firewall and the other leak global context
|
|
50
50
|
into an ``off``/``override`` persona): lowercase → collapse invalid runs to
|
|
51
|
-
"-" → strip leading -/_ → truncate 64 → validate. "
|
|
51
|
+
"-" → strip leading -/_ → truncate 64 → validate. "Alice" → "alice";
|
|
52
52
|
"MacBook-Pro.local" → "macbook-pro-local"; all-symbols → None."""
|
|
53
53
|
if not isinstance(raw, str):
|
|
54
54
|
return None
|
|
@@ -73,7 +73,7 @@ def _resolve_agent_name(cfg: Dict[str, Any]) -> Optional[str]:
|
|
|
73
73
|
2. ``HERMES_PROFILE`` env;
|
|
74
74
|
3. parse ``HERMES_HOME`` when it ends ``profiles/<name>``;
|
|
75
75
|
4. None → bare fetch → the global set.
|
|
76
|
-
Each source is stripped then SANITIZED (not rejected) so "
|
|
76
|
+
Each source is stripped then SANITIZED (not rejected) so "Alice" → "alice"
|
|
77
77
|
matches the TS contract; a source that sanitizes to None yields None (bare
|
|
78
78
|
fetch), never a fall-through to another identity."""
|
|
79
79
|
configured = (cfg.get("agent_name") or "").strip()
|
package/openclaw.plugin.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"serverUrl": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"default": "http://127.0.0.1:8787",
|
|
14
|
-
"description": "Hicortex server URL. Defaults to localhost (co-located server). For multi-machine setups, point this at the remote server (e.g. http://
|
|
14
|
+
"description": "Hicortex server URL. Defaults to localhost (co-located server). For multi-machine setups, point this at the remote server (e.g. http://your-server:8787 or an HTTPS URL)."
|
|
15
15
|
},
|
|
16
16
|
"authToken": {
|
|
17
17
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Self-learning memory for AI agents \u2014 experience captured automatically, distilled into lessons overnight, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|