@gamaze/hicortex 0.12.0 → 0.13.0

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.
@@ -23,30 +23,27 @@
23
23
  * CC session, and a broken /context fetch must never blank the whole output.
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.titleCaseSection = titleCaseSection;
27
+ exports.orderSectionNames = orderSectionNames;
28
+ exports.renderContextBlock = renderContextBlock;
29
+ exports.gateAndRenderContext = gateAndRenderContext;
26
30
  exports.fetchLessonsContext = fetchLessonsContext;
27
31
  const node_fs_1 = require("node:fs");
28
32
  const node_path_1 = require("node:path");
29
- const node_os_1 = require("node:os");
33
+ const context_store_js_1 = require("./context-store.js");
30
34
  const features_js_1 = require("./features.js");
31
35
  const extensions_js_1 = require("./extensions.js");
32
36
  const state_js_1 = require("./state.js");
37
+ const paths_js_1 = require("./paths.js");
33
38
  const DEFAULT_PORT = 8787;
34
39
  /** Harness name this hook injects for — used to self-gate on GET /context `clients`. */
35
40
  const THIS_HARNESS = "cc";
36
- /**
37
- * Resolve the Hicortex home. Honors HICORTEX_HOME (matches the HICORTEX_DB_PATH
38
- * env convention in db.ts) so headless/scratch installs can point elsewhere;
39
- * defaults to ~/.hicortex. Resolved per-call so tests and env changes apply.
40
- */
41
- function hicortexHome() {
42
- return process.env.HICORTEX_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
43
- }
44
41
  /**
45
42
  * Read ~/.hicortex/config.json and resolve the server URL + auth token, or
46
43
  * null when there is no usable config (server not set up yet — fail soft).
47
44
  */
48
45
  function resolveConfig() {
49
- const home = hicortexHome();
46
+ const home = (0, paths_js_1.hicortexHome)();
50
47
  let config;
51
48
  try {
52
49
  config = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(home, "config.json"), "utf-8"));
@@ -58,7 +55,13 @@ function resolveConfig() {
58
55
  const serverUrl = config.mode === "client" && typeof config.serverUrl === "string"
59
56
  ? config.serverUrl.replace(/\/+$/, "")
60
57
  : `http://127.0.0.1:${config.port ?? DEFAULT_PORT}`;
61
- return { serverUrl, authToken: config.authToken, home };
58
+ // Per-agent context id (0.13) via the shared resolver, so the id sent here
59
+ // always matches what `hicortex status` reports. No configured agentName →
60
+ // agentId null → NO ?agent= (bare fetch): CC's default is the shared global
61
+ // context. A configured agentName that sanitizes to null → agentId null too
62
+ // (NO ?agent=), never a 400 that the fail-soft hook would silently swallow.
63
+ const agentName = (0, context_store_js_1.resolveAgentIdentity)(config).agentId;
64
+ return { serverUrl, authToken: config.authToken, home, agentName };
62
65
  }
63
66
  function authHeaders(authToken) {
64
67
  return authToken ? { "Authorization": `Bearer ${authToken}` } : {};
@@ -120,6 +123,8 @@ async function fetchLessonsBlock(cfg) {
120
123
  /**
121
124
  * Title-case a section name for its heading: split on `-`/`_`, capitalize each
122
125
  * word ("user" → "User", "my_notes" → "My Notes").
126
+ * Exported so the OC plugin (index.ts) renders the `## Context` block
127
+ * identically to the CC hook rather than duplicating the logic.
123
128
  */
124
129
  function titleCaseSection(name) {
125
130
  return name
@@ -132,7 +137,7 @@ function titleCaseSection(name) {
132
137
  * Stable section ordering: `user` first, then `rules` (the seeded primary
133
138
  * sections, spec §8), then every other section alphabetically. Server-side
134
139
  * enumeration order (readdirSync) is FS-dependent, so we sort here for a
135
- * deterministic injection block.
140
+ * deterministic injection block. Exported for reuse by the OC plugin.
136
141
  */
137
142
  function orderSectionNames(names) {
138
143
  const primaries = ["user", "rules"].filter((p) => names.includes(p));
@@ -140,23 +145,13 @@ function orderSectionNames(names) {
140
145
  return [...primaries, ...rest];
141
146
  }
142
147
  /**
143
- * Fetch /context and build the `## Context` block, or null when nothing should
144
- * be injected: non-2xx, this harness not in `clients`, no sections, or all
145
- * sections empty. Throws propagate to the caller's fail-soft catch.
148
+ * Render the `## Context` block from a resolved section map, or null when there
149
+ * is nothing to inject (no sections, or every section blank after trimming).
150
+ * Pure — no gating, no I/O. Shared verbatim by the CC hook and the OC plugin so
151
+ * both harnesses emit an identical block. Sections are ordered (user, rules,
152
+ * then alphabetical) and rendered under title-cased `###` headings.
146
153
  */
147
- async function fetchContextBlock(cfg) {
148
- const resp = await fetch(`${cfg.serverUrl}/context`, {
149
- headers: authHeaders(cfg.authToken),
150
- signal: AbortSignal.timeout(3000),
151
- });
152
- if (!resp.ok)
153
- return null;
154
- const data = await resp.json();
155
- // Self-gate: only inject when this harness is in the server-resolved list.
156
- const clients = Array.isArray(data.clients) ? data.clients : [];
157
- if (!clients.includes(THIS_HARNESS))
158
- return null;
159
- const sections = data.sections;
154
+ function renderContextBlock(sections) {
160
155
  if (!sections || typeof sections !== "object" || Array.isArray(sections))
161
156
  return null;
162
157
  const names = orderSectionNames(Object.keys(sections));
@@ -171,6 +166,62 @@ async function fetchContextBlock(cfg) {
171
166
  return null;
172
167
  return ["## Context", "", ...bodyParts].join("\n");
173
168
  }
169
+ /**
170
+ * Gate a GET /context response and render the `## Context` block, or null when
171
+ * nothing should be injected: `harness` not in the server-resolved `clients`,
172
+ * an empty/blank section set, or — when `requireAgentEcho` — a response that
173
+ * does not echo `agent`. The SINGLE gate used by both CC and OC so the two can
174
+ * never drift (the Python Hermes plugin `provider.py::_context_block` mirrors
175
+ * this logic — keep them in sync).
176
+ *
177
+ * `requireAgentEcho` is the old-server guard, and it is the CALLER's decision:
178
+ * - OC passes `agentId !== null` — when it actually sent an id, a 0.12 server
179
+ * that ignores `?agent=` (200 global, no echo) must NOT leak global context
180
+ * into every persona; on a bare fetch (no id) the guard is off (amendment
181
+ * A2).
182
+ * - CC passes `false` ALWAYS and deliberately (see the call site): a thin CC
183
+ * client auto-upgrades via npx BEFORE bedrock does, so during the upgrade
184
+ * window it talks to a 0.12 server that cannot hold ANY per-agent config —
185
+ * global IS the operator's intended state there, and a guard would instead
186
+ * blank ALL context for every CC session in that window.
187
+ */
188
+ function gateAndRenderContext(data, harness, opts) {
189
+ if (!data || typeof data !== "object")
190
+ return null;
191
+ const clients = Array.isArray(data.clients) ? data.clients : [];
192
+ if (!clients.includes(harness))
193
+ return null;
194
+ if (opts.requireAgentEcho && typeof data.agent !== "string")
195
+ return null;
196
+ return renderContextBlock(data.sections ?? {});
197
+ }
198
+ /**
199
+ * Fetch /context and build the `## Context` block, or null when nothing should
200
+ * be injected: non-2xx, this harness not in `clients`, no sections, or all
201
+ * sections empty. Throws propagate to the caller's fail-soft catch.
202
+ */
203
+ async function fetchContextBlock(cfg) {
204
+ // Send ?agent= only when we have a valid id; the server does the merge and
205
+ // returns the resolved sections, so the hook stays dumb (no client-side mode
206
+ // logic). A null id (CC's default: no configured agentName, or a configured
207
+ // value that sanitizes to nothing) → bare /context → the shared global set.
208
+ const url = cfg.agentName
209
+ ? `${cfg.serverUrl}/context?agent=${encodeURIComponent(cfg.agentName)}`
210
+ : `${cfg.serverUrl}/context`;
211
+ const resp = await fetch(url, {
212
+ headers: authHeaders(cfg.authToken),
213
+ signal: AbortSignal.timeout(3000),
214
+ });
215
+ if (!resp.ok)
216
+ return null;
217
+ const data = await resp.json();
218
+ // CC deliberately passes requireAgentEcho: false (NOT the OC/Hermes old-server
219
+ // guard). A thin CC client auto-upgrades via npx BEFORE bedrock does, so
220
+ // mid-upgrade it may hit a 0.12 server that returns global context with no
221
+ // `agent` echo — and a 0.12 server cannot hold per-agent config, so global is
222
+ // the intended state. Guarding here would blank ALL CC context in that window.
223
+ return gateAndRenderContext(data, THIS_HARNESS, { requireAgentEcho: false });
224
+ }
174
225
  /**
175
226
  * Fetch context + lessons concurrently and return the combined Markdown block,
176
227
  * or null when neither yields anything (nothing to inject; caller prints
@@ -81,6 +81,10 @@ let stateDir = "";
81
81
  // Resolved contextClients list (spec §2) — the harness names allowed to inject
82
82
  // the standing context layer. Echoed by GET /context so each hook self-gates.
83
83
  let contextClients = ["cc"];
84
+ // Resolved contextAgents map (0.13) — agent id → mode (override/global/off).
85
+ // Read once at boot (like contextClients); the drop-in-a-dir presence path is
86
+ // per-request, so only explicit config entries need a daemon restart to apply.
87
+ let contextAgents = {};
84
88
  // Cache detectChunkSize results keyed by "<provider>/<model>@<baseUrl>" so we
85
89
  // probe each endpoint once per server boot rather than once per /distill request.
86
90
  const chunkSizeCache = new Map();
@@ -122,7 +126,7 @@ function createMcpServer() {
122
126
  if (!db)
123
127
  return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
124
128
  try {
125
- const results = retrieval.searchContext(db, { project, limit });
129
+ const results = retrieval.searchRecent(db, { project, limit });
126
130
  return { content: [{ type: "text", text: formatResults(results) }] };
127
131
  }
128
132
  catch (err) {
@@ -436,6 +440,16 @@ async function startServer(options = {}) {
436
440
  console.warn(`[hicortex] Ignoring unknown contextClients: ${resolvedClients.dropped.join(", ")} ` +
437
441
  `(known: cc, hermes, oc)`);
438
442
  }
443
+ // Per-agent context (0.13): resolve the config-declared modes. Warn once per
444
+ // boot on dropped entries (bad key or bad mode) so typos surface. NOTE: this
445
+ // map is boot-time; editing contextAgents needs a daemon restart. Dropping an
446
+ // agents/<id> dir onto disk takes effect immediately (per-request presence).
447
+ const resolvedAgents = (0, context_store_js_1.resolveContextAgents)(savedConfig?.contextAgents);
448
+ contextAgents = resolvedAgents.agents;
449
+ if (resolvedAgents.dropped.length > 0) {
450
+ console.warn(`[hicortex] Ignoring invalid contextAgents entries: ${resolvedAgents.dropped.join(", ")} ` +
451
+ `(keys must match ^[a-z0-9][a-z0-9_-]*$; modes must be override|global|off)`);
452
+ }
439
453
  // Express app
440
454
  const app = (0, express_1.default)();
441
455
  // Raise the body limit — whole-session denoised transcripts exceed the 100 kB default.
@@ -593,7 +607,7 @@ async function startServer(options = {}) {
593
607
  ? req.query.privacy.split(",").map((s) => s.trim()).filter(Boolean)
594
608
  : undefined;
595
609
  try {
596
- const results = retrieval.searchContext(db, { project, limit, privacy });
610
+ const results = retrieval.searchRecent(db, { project, limit, privacy });
597
611
  res.json({ results });
598
612
  }
599
613
  catch (err) {
@@ -620,7 +634,7 @@ async function startServer(options = {}) {
620
634
  // which the tests exercise directly — no mirror-app drift.
621
635
  app.get("/context", (req, res) => {
622
636
  try {
623
- const r = (0, context_store_js_1.handleContextGet)((0, node_path_1.join)(stateDir, "context"), contextClients, req.query);
637
+ const r = (0, context_store_js_1.handleContextGet)((0, node_path_1.join)(stateDir, "context"), contextClients, req.query, contextAgents);
624
638
  res.status(r.status).json(r.body);
625
639
  }
626
640
  catch (err) {
@@ -629,7 +643,7 @@ async function startServer(options = {}) {
629
643
  });
630
644
  app.put("/context", (req, res) => {
631
645
  try {
632
- const r = (0, context_store_js_1.handleContextPut)((0, node_path_1.join)(stateDir, "context"), req.body);
646
+ const r = (0, context_store_js_1.handleContextPut)((0, node_path_1.join)(stateDir, "context"), req.body, req.query, contextAgents);
633
647
  if (r.warn)
634
648
  console.warn(`[hicortex] ${r.warn}`);
635
649
  res.status(r.status).json(r.body);
@@ -11,13 +11,14 @@
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.showNightlyStatus = showNightlyStatus;
14
+ const paths_js_1 = require("./paths.js");
14
15
  const node_fs_1 = require("node:fs");
15
16
  const node_path_1 = require("node:path");
16
17
  const node_os_1 = require("node:os");
17
18
  const node_child_process_1 = require("node:child_process");
18
19
  const db_js_1 = require("./db.js");
19
20
  const state_js_1 = require("./state.js");
20
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
21
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
21
22
  const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
22
23
  async function showNightlyStatus() {
23
24
  console.log("Hicortex Nightly Pipeline Status");
package/dist/nightly.js CHANGED
@@ -46,9 +46,9 @@ var __importStar = (this && this.__importStar) || (function () {
46
46
  })();
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.runNightly = runNightly;
49
+ const paths_js_1 = require("./paths.js");
49
50
  const node_fs_1 = require("node:fs");
50
51
  const node_path_1 = require("node:path");
51
- const node_os_1 = require("node:os");
52
52
  let VERSION = "0.0.0";
53
53
  try {
54
54
  VERSION = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8")).version;
@@ -69,7 +69,7 @@ const oc_transcript_reader_js_1 = require("./oc-transcript-reader.js");
69
69
  const features_js_1 = require("./features.js");
70
70
  const state_js_1 = require("./state.js");
71
71
  const telemetry_js_1 = require("./telemetry.js");
72
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
72
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
73
73
  function readNightlyConfig(stateDir) {
74
74
  try {
75
75
  const configPath = (0, node_path_1.join)(stateDir, "config.json");
@@ -0,0 +1 @@
1
+ export declare function hicortexHome(): string;
package/dist/paths.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hicortexHome = hicortexHome;
4
+ /**
5
+ * Canonical Hicortex home resolution — the single source of truth (#174).
6
+ *
7
+ * Honors the HICORTEX_HOME env override (a headless/test seam, mirroring the
8
+ * HICORTEX_DB_PATH convention in db.ts); otherwise defaults to ~/.hicortex.
9
+ * Every module that needs the home dir routes through here, so the override
10
+ * behaves consistently across all commands instead of being honored by some
11
+ * (context-cli, lessons-context) and hardcoded away by others.
12
+ */
13
+ const node_os_1 = require("node:os");
14
+ const node_path_1 = require("node:path");
15
+ function hicortexHome() {
16
+ return process.env.HICORTEX_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
17
+ }
package/dist/relink.js CHANGED
@@ -74,14 +74,14 @@ var __importStar = (this && this.__importStar) || (function () {
74
74
  Object.defineProperty(exports, "__esModule", { value: true });
75
75
  exports.getStoredEmbedding = getStoredEmbedding;
76
76
  exports.runRelink = runRelink;
77
+ const paths_js_1 = require("./paths.js");
77
78
  const node_fs_1 = require("node:fs");
78
79
  const node_path_1 = require("node:path");
79
- const node_os_1 = require("node:os");
80
80
  const db_js_1 = require("./db.js");
81
81
  const storage = __importStar(require("./storage.js"));
82
82
  const consolidate_js_1 = require("./consolidate.js");
83
83
  const state_js_1 = require("./state.js");
84
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
84
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
85
85
  function readConfig(stateDir) {
86
86
  try {
87
87
  return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(stateDir, "config.json"), "utf-8"));
@@ -55,7 +55,7 @@ export declare function retrieve(db: Database.Database, embedFn: EmbedFn, query:
55
55
  /**
56
56
  * Get recent context, optionally filtered by project and privacy.
57
57
  */
58
- export declare function searchContext(db: Database.Database, options?: {
58
+ export declare function searchRecent(db: Database.Database, options?: {
59
59
  project?: string | null;
60
60
  limit?: number;
61
61
  privacy?: string[];
package/dist/retrieval.js CHANGED
@@ -52,7 +52,7 @@ exports.l2ToCosine = l2ToCosine;
52
52
  exports.effectiveStrength = effectiveStrength;
53
53
  exports.computeScore = computeScore;
54
54
  exports.retrieve = retrieve;
55
- exports.searchContext = searchContext;
55
+ exports.searchRecent = searchRecent;
56
56
  const storage = __importStar(require("./storage.js"));
57
57
  const BASE_DECAY = 0.0005;
58
58
  /**
@@ -315,7 +315,7 @@ async function retrieve(db, embedFn, query, options) {
315
315
  /**
316
316
  * Get recent context, optionally filtered by project and privacy.
317
317
  */
318
- function searchContext(db, options) {
318
+ function searchRecent(db, options) {
319
319
  const limit = options?.limit ?? 10;
320
320
  const project = options?.project;
321
321
  const privacy = options?.privacy;
package/dist/state.js CHANGED
@@ -23,10 +23,10 @@ exports.saveState = saveState;
23
23
  exports.updateState = updateState;
24
24
  exports.migrateLegacyState = migrateLegacyState;
25
25
  exports.describeLastNightly = describeLastNightly;
26
+ const paths_js_1 = require("./paths.js");
26
27
  const node_fs_1 = require("node:fs");
27
28
  const node_path_1 = require("node:path");
28
- const node_os_1 = require("node:os");
29
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
29
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
30
30
  const STATE_FILE = "state.json";
31
31
  /**
32
32
  * Load the state file. Returns an empty state if the file is missing
package/dist/status.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  /**
2
2
  * Hicortex status — show current configuration and stats.
3
3
  */
4
+ /**
5
+ * The value shown after "Agent name:" in `hicortex status` (#179, A3). Reports
6
+ * EXACTLY what the CC hook resolves (shared `resolveAgentIdentity`), so the
7
+ * operator never keys `contextAgents`/`agents/<id>/` on an id the install does
8
+ * not actually send. Unset → the install sends no `?agent=` and shares the
9
+ * global context (CC default). A configured-but-unsanitizable value is called
10
+ * out as invalid (the hook sends none) rather than silently accepted.
11
+ */
12
+ export declare function statusAgentLine(config: Record<string, unknown>): string;
4
13
  export declare function runStatus(): Promise<void>;
package/dist/status.js CHANGED
@@ -3,7 +3,9 @@
3
3
  * Hicortex status — show current configuration and stats.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.statusAgentLine = statusAgentLine;
6
7
  exports.runStatus = runStatus;
8
+ const paths_js_1 = require("./paths.js");
7
9
  const node_fs_1 = require("node:fs");
8
10
  const node_path_1 = require("node:path");
9
11
  const node_os_1 = require("node:os");
@@ -11,9 +13,29 @@ const node_child_process_1 = require("node:child_process");
11
13
  const db_js_1 = require("./db.js");
12
14
  const features_js_1 = require("./features.js");
13
15
  const state_js_1 = require("./state.js");
14
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
16
+ const context_store_js_1 = require("./context-store.js");
17
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
15
18
  const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
16
19
  const OC_CONFIG = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
20
+ /**
21
+ * The value shown after "Agent name:" in `hicortex status` (#179, A3). Reports
22
+ * EXACTLY what the CC hook resolves (shared `resolveAgentIdentity`), so the
23
+ * operator never keys `contextAgents`/`agents/<id>/` on an id the install does
24
+ * not actually send. Unset → the install sends no `?agent=` and shares the
25
+ * global context (CC default). A configured-but-unsanitizable value is called
26
+ * out as invalid (the hook sends none) rather than silently accepted.
27
+ */
28
+ function statusAgentLine(config) {
29
+ const id = (0, context_store_js_1.resolveAgentIdentity)(config);
30
+ switch (id.source) {
31
+ case "configured":
32
+ return id.agentId;
33
+ case "invalid-config":
34
+ return `(invalid configured value "${id.rawConfigured}" — fix config.agentName; hook sends none)`;
35
+ default: // unset
36
+ return "(not set — global context)";
37
+ }
38
+ }
17
39
  async function runStatus() {
18
40
  console.log("Hicortex Status");
19
41
  console.log("─".repeat(40));
@@ -41,11 +63,12 @@ async function runStatus() {
41
63
  let licenseKey = "";
42
64
  let savedAuthToken = "";
43
65
  let isClientMode = false;
66
+ let parsedConfig = {};
44
67
  try {
45
- const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
46
- licenseKey = config.licenseKey ?? "";
47
- savedAuthToken = config.authToken ?? "";
48
- isClientMode = config.mode === "client";
68
+ parsedConfig = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
69
+ licenseKey = parsedConfig.licenseKey ?? "";
70
+ savedAuthToken = parsedConfig.authToken ?? "";
71
+ isClientMode = parsedConfig.mode === "client";
49
72
  }
50
73
  catch { /* no config */ }
51
74
  const validated = (0, features_js_1.getValidatedLicense)();
@@ -64,6 +87,9 @@ async function runStatus() {
64
87
  else if (!isClientMode && !savedAuthToken) {
65
88
  console.log(`Auth token: not configured (run: npx @gamaze/hicortex init)`);
66
89
  }
90
+ // Per-agent context id (#179) — the id this install sends as ?agent= and the
91
+ // key operators use for contextAgents / agents/<id>/ dirs.
92
+ console.log(`Agent name: ${statusAgentLine(parsedConfig)}`);
67
93
  console.log();
68
94
  // Adapters
69
95
  console.log("Adapters:");
package/dist/uninstall.js CHANGED
@@ -5,13 +5,14 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.runUninstall = runUninstall;
8
+ const paths_js_1 = require("./paths.js");
8
9
  const node_fs_1 = require("node:fs");
9
10
  const node_path_1 = require("node:path");
10
11
  const node_os_1 = require("node:os");
11
12
  const node_child_process_1 = require("node:child_process");
12
13
  const node_readline_1 = require("node:readline");
13
14
  const claude_md_js_1 = require("./claude-md.js");
14
- const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
15
+ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
15
16
  const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
16
17
  const CC_COMMANDS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "commands");
17
18
  const CLAUDE_MD = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "CLAUDE.md");
@@ -14,11 +14,24 @@ Gives [Hermes](https://github.com/nousresearch/hermes-agent) agents self-learnin
14
14
  |---|---|---|
15
15
  | `prefetch(query)` | recall relevant memories before each turn | `GET /search` |
16
16
  | `queue_prefetch(query)` | background recall for the next turn | `GET /search` |
17
- | `system_prompt_block()` | inject distilled lessons + memory index | `GET /lessons` |
17
+ | `system_prompt_block()` | inject per-agent standing context + distilled lessons + memory index | `GET /context`, `GET /lessons` |
18
18
  | `get_tool_schemas()` | exposes the 8 unified tools | see tool table below |
19
19
 
20
20
  That's the whole surface. No `sync_turn`, no compaction/session-end capture — those are intentionally absent.
21
21
 
22
+ ### Per-agent standing context (0.13)
23
+
24
+ `system_prompt_block()` also injects the hand-edited **standing context layer** (`## Context`, above the lessons block) — "who you are + how to work", distinct from episodic memory. The server resolves it **per agent**: this profile's own sections override the global set (`override`), or it can be `global` or `off`. See the main repo's `/context` layer docs.
25
+
26
+ The plugin sends its **profile name** as `?agent=`, resolved in this order:
27
+
28
+ 1. `agent_name` in the plugin config (explicit override);
29
+ 2. the `HERMES_PROFILE` environment variable;
30
+ 3. a `HERMES_HOME` ending in `profiles/<name>` (the per-profile install path);
31
+ 4. none → the global context (backward compatible).
32
+
33
+ Leave `agent_name` blank to auto-derive (2–4). Context injection needs a Hicortex server **≥ 0.13**; against an older server the plugin detects the missing per-agent support and injects no context (lessons are unaffected). Context and lessons fail soft independently — a context failure never costs the lessons block.
34
+
22
35
  ### Tools (unified 8)
23
36
 
24
37
  | Tool | REST call | Description |
@@ -57,7 +70,7 @@ hermes memory setup # select "hicortex", enter the server URL/token when promp
57
70
 
58
71
  Run it once per profile if you use Hermes profiles. Hermes allows **one** external memory provider at a time, so disable Honcho (or any other) first, then restart the gateway.
59
72
 
60
- Config fields (`hicortex_url`, `default_project`, `recall_limit`, `privacy_filter`) can also be written to `$HERMES_HOME/plugins/hicortex/config.json` directly. The auth token is a **secret** — set it via env, not the JSON file:
73
+ Config fields (`hicortex_url`, `default_project`, `recall_limit`, `privacy_filter`, `agent_name`) can also be written to `$HERMES_HOME/plugins/hicortex/config.json` directly. `agent_name` pins the per-agent context id for this profile (leave blank to auto-derive — see [Per-agent standing context](#per-agent-standing-context-013)). The auth token is a **secret** — set it via env, not the JSON file:
61
74
 
62
75
  ```bash
63
76
  export HICORTEX_AUTH_TOKEN=hctx-default-token # or your custom token
@@ -96,6 +96,13 @@ class HicortexClient:
96
96
  def lessons(self) -> dict[str, Any]:
97
97
  return self._get("/lessons")
98
98
 
99
+ def context(self, agent: Optional[str] = None) -> dict[str, Any]:
100
+ """Standing context layer (L2). When ``agent`` is set, the server
101
+ resolves the per-agent scope and echoes ``agent``/``mode`` (0.13); a
102
+ pre-0.13 server ignores the param and returns the global set with no
103
+ echo — the caller uses that echo as an old-server guard."""
104
+ return self._get("/context", {"agent": agent})
105
+
99
106
  def index(self) -> dict[str, Any]:
100
107
  return self._get("/index")
101
108
 
@@ -57,6 +57,16 @@ CONFIG_SCHEMA: list[dict[str, Any]] = [
57
57
  "default": "WORK,PERSONAL",
58
58
  "required": False,
59
59
  },
60
+ {
61
+ "key": "agent_name",
62
+ "label": "Agent name (per-agent context)",
63
+ "description": (
64
+ "Identity sent as ?agent= when fetching the standing context layer, "
65
+ "so this profile gets its own context (0.13). Leave blank to "
66
+ "auto-derive from the running profile (HERMES_PROFILE / HERMES_HOME)."
67
+ ),
68
+ "required": False,
69
+ },
60
70
  # NOTE: recall-only plugin — no capture config. Capture is handled by the
61
71
  # nightly server-side reader of each agent's session store.
62
72
  ]
@@ -1,6 +1,6 @@
1
1
  name: hicortex
2
- version: 0.5.0
3
- description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn and exposes the full 8-tool memory surface (search, recent, ingest, lessons, index, graph, update, delete) via a shared Hicortex server. Stdlib-only."
2
+ version: 0.6.0
3
+ description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn plus a per-agent standing context block, and exposes the full 8-tool memory surface (search, recent, ingest, lessons, index, graph, update, delete) via a shared Hicortex server. Stdlib-only."
4
4
  pip_dependencies: []
5
5
  hooks: []
6
6
  requires_env: