@gamaze/hicortex 0.19.0 → 0.19.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 CHANGED
@@ -54,7 +54,21 @@ openclaw plugins install @gamaze/hicortex
54
54
  openclaw gateway restart
55
55
  ```
56
56
 
57
- The plugin connects to `http://127.0.0.1:8787` by default. For a remote server, add `serverUrl` and `authToken` (find the token via `hicortex status` on the server) to the plugin config in `~/.openclaw/openclaw.json`.
57
+ The plugin connects to `http://127.0.0.1:8787` by default. For a remote server, add `serverUrl` and `authToken` (find the token via `hicortex status` on the server) to the plugin's config section in `~/.openclaw/openclaw.json`:
58
+
59
+ ```json
60
+ {
61
+ "plugins": {
62
+ "entries": {
63
+ "hicortex": {
64
+ "config": { "serverUrl": "http://your-server:8787", "authToken": "hctx-…" }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ Bare top-level keys (`"serverUrl": …` at the root of `openclaw.json`) still work — legacy compat — but the nested form above is canonical. An empty nested `config` object is ignored rather than shadowing top-level keys.
58
72
 
59
73
  ## Requirements
60
74
 
@@ -399,7 +413,7 @@ The nightly and the server behave differently on purpose: a malformed config mak
399
413
 
400
414
  **Tools not visible to agent (OC):** The plugin auto-adds tools to `tools.allow` on startup. Restart the gateway after install.
401
415
 
402
- **OC plugin: "Server unreachable":** The plugin requires a running Hicortex server. Run `npx @gamaze/hicortex init` on the same machine, or set `serverUrl` in the plugin config to point at a remote server.
416
+ **OC plugin: "Server unreachable":** The plugin requires a running Hicortex server. Run `npx @gamaze/hicortex init` on the same machine, or set `serverUrl` in the plugin config (`plugins.entries.hicortex.config` in `~/.openclaw/openclaw.json`; bare top-level keys also work) to point at a remote server.
403
417
 
404
418
  **LLM auto-config failed:** Check logs for `[hicortex] WARNING`. Add `llmBaseUrl` to plugin config or set `HICORTEX_LLM_BASE_URL` env var (applies to server setup, not the OC plugin itself).
405
419
 
package/dist/index.d.ts CHANGED
@@ -23,13 +23,37 @@
23
23
  * machine's Hicortex nightly reads them via oc-transcript-reader.ts —
24
24
  * canonical nightly-from-logs, same as CC JSONL and Hermes state.db.
25
25
  */
26
- import type { MemorySearchResult } from "./types.js";
26
+ import type { HicortexConfig, MemorySearchResult } from "./types.js";
27
27
  export declare function formatToolResults(results: MemorySearchResult[]): {
28
28
  content: Array<{
29
29
  type: string;
30
30
  text: string;
31
31
  }>;
32
32
  };
33
+ /**
34
+ * Resolve the plugin's config from the raw `ctx.config` OC hands the service
35
+ * (the ENTIRE openclaw.json — verified against gateway-cli createServiceContext:
36
+ * config = params.cfg — not a per-plugin section). Stateless: touches no module
37
+ * state, never mutates the input, never throws (console.warn is its only side
38
+ * effect). Three branches, first match wins:
39
+ *
40
+ * 1. `plugins.entries.hicortex.config` — the canonical OC per-plugin
41
+ * section. Only eligible when it is a non-null object with ≥1 OWN key:
42
+ * OC scaffolds `config: {}` for an installed-but-unconfigured plugin,
43
+ * and that empty object must not shadow real config further down.
44
+ * 2. `hicortex` at the top level — but only when `typeof === "object"`:
45
+ * a string/bool/number there (e.g. `"hicortex": true` as a feature
46
+ * toggle) is not a config and is skipped, not cast.
47
+ * 3. the top level itself — bare keys (`serverUrl`, `authToken`, …) at the
48
+ * root of openclaw.json; the pre-0.19 shape, kept for backcompat.
49
+ *
50
+ * `serverUrl` and `authToken` are validated at this boundary: a present but
51
+ * non-string (or empty-string) value warns naming the key and degrades —
52
+ * serverUrl falls back to DEFAULT_SERVER_URL, authToken to undefined. No
53
+ * throw paths: a malformed config degrades to defaults instead of leaving
54
+ * the plugin half-initialized.
55
+ */
56
+ export declare function resolveOcPluginConfig(raw: unknown): HicortexConfig;
33
57
  declare const _default: {
34
58
  id: string;
35
59
  name: string;
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@
26
26
  */
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
28
  exports.formatToolResults = formatToolResults;
29
+ exports.resolveOcPluginConfig = resolveOcPluginConfig;
29
30
  const paths_js_1 = require("./paths.js");
30
31
  const features_js_1 = require("./features.js");
31
32
  const extensions_js_1 = require("./extensions.js");
@@ -256,6 +257,104 @@ function formatToolResults(results) {
256
257
  return { content: [{ type: "text", text }] };
257
258
  }
258
259
  // ---------------------------------------------------------------------------
260
+ // Config resolution
261
+ // ---------------------------------------------------------------------------
262
+ /** Object (not null, not array) → itself as a record; anything else → undefined. */
263
+ function isRecord(v) {
264
+ return typeof v === "object" && v !== null && !Array.isArray(v)
265
+ ? v
266
+ : undefined;
267
+ }
268
+ function isNonEmptyString(v) {
269
+ return typeof v === "string" && v.length > 0;
270
+ }
271
+ /** Human name for a config value that failed validation. Only ever called
272
+ * with INVALID values (non-strings and empty strings), so the string branch
273
+ * means "empty string". */
274
+ function describeInvalid(v) {
275
+ if (v === null)
276
+ return "null";
277
+ if (Array.isArray(v))
278
+ return "an array";
279
+ if (typeof v === "string")
280
+ return "an empty string";
281
+ return typeof v === "object" ? "an object" : `a ${typeof v}`;
282
+ }
283
+ /**
284
+ * Resolve the plugin's config from the raw `ctx.config` OC hands the service
285
+ * (the ENTIRE openclaw.json — verified against gateway-cli createServiceContext:
286
+ * config = params.cfg — not a per-plugin section). Stateless: touches no module
287
+ * state, never mutates the input, never throws (console.warn is its only side
288
+ * effect). Three branches, first match wins:
289
+ *
290
+ * 1. `plugins.entries.hicortex.config` — the canonical OC per-plugin
291
+ * section. Only eligible when it is a non-null object with ≥1 OWN key:
292
+ * OC scaffolds `config: {}` for an installed-but-unconfigured plugin,
293
+ * and that empty object must not shadow real config further down.
294
+ * 2. `hicortex` at the top level — but only when `typeof === "object"`:
295
+ * a string/bool/number there (e.g. `"hicortex": true` as a feature
296
+ * toggle) is not a config and is skipped, not cast.
297
+ * 3. the top level itself — bare keys (`serverUrl`, `authToken`, …) at the
298
+ * root of openclaw.json; the pre-0.19 shape, kept for backcompat.
299
+ *
300
+ * `serverUrl` and `authToken` are validated at this boundary: a present but
301
+ * non-string (or empty-string) value warns naming the key and degrades —
302
+ * serverUrl falls back to DEFAULT_SERVER_URL, authToken to undefined. No
303
+ * throw paths: a malformed config degrades to defaults instead of leaving
304
+ * the plugin half-initialized.
305
+ */
306
+ function resolveOcPluginConfig(raw) {
307
+ const warn = (msg) => console.warn(`[hicortex] WARNING: ${msg}`);
308
+ const full = isRecord(raw) ?? {};
309
+ // Branch 1 — isRecord at every level: a missing key, string, array, or
310
+ // null anywhere in the chain just falls through to the next branch.
311
+ const plugins = isRecord(full.plugins);
312
+ const entries = isRecord(plugins?.entries);
313
+ const entry = isRecord(entries?.hicortex);
314
+ const entryConfig = isRecord(entry?.config);
315
+ const nested = entryConfig !== undefined && Object.keys(entryConfig).length > 0
316
+ ? entryConfig
317
+ : undefined;
318
+ // Branch 2 — top-level `hicortex`, object-guarded (see doc block).
319
+ const hicortexObj = isRecord(full.hicortex);
320
+ const winner = nested ?? hicortexObj ?? full;
321
+ const winnerPath = nested !== undefined
322
+ ? "plugins.entries.hicortex.config"
323
+ : hicortexObj !== undefined ? "hicortex" : "the top level of openclaw.json";
324
+ // Copy, never the caller's object: sanitizing below must not mutate
325
+ // ctx.config, and pluginConfig must not alias OC's config state.
326
+ const resolved = { ...winner };
327
+ const rawUrl = winner.serverUrl;
328
+ if (rawUrl !== undefined && !isNonEmptyString(rawUrl)) {
329
+ warn(`plugin config key "serverUrl" must be a non-empty string (got ${describeInvalid(rawUrl)}) ` +
330
+ `— falling back to ${DEFAULT_SERVER_URL}`);
331
+ resolved.serverUrl = DEFAULT_SERVER_URL;
332
+ }
333
+ const rawToken = winner.authToken;
334
+ if (rawToken !== undefined && !isNonEmptyString(rawToken)) {
335
+ warn(`plugin config key "authToken" must be a non-empty string (got ${describeInvalid(rawToken)}) — ignoring it`);
336
+ resolved.authToken = undefined;
337
+ }
338
+ // Shadow detection (F2) — two configs disagreeing, surfaced instead of
339
+ // silently honoring one of them. Case 1: an OC-scaffolded EMPTY
340
+ // plugins.entries.hicortex.config was skipped while a bare top-level
341
+ // serverUrl exists (the exact shape the ≥1-own-key rule exists for).
342
+ // Gate on the ACTUAL winner (re-CR F1): in the compound shape (empty
343
+ // nested config + a top-level hicortex object + bare serverUrl) the hicortex
344
+ // object wins — the warn must not claim the bare key is being used.
345
+ if (nested === undefined && entryConfig !== undefined && winner === full && isNonEmptyString(full.serverUrl)) {
346
+ warn(`plugins.entries.hicortex.config is empty, so the top-level serverUrl is used instead — ` +
347
+ `remove the empty config section or move serverUrl into it`);
348
+ }
349
+ // Case 2: a real nested/hicortex section won the chain but carries no valid
350
+ // serverUrl, while a bare top-level serverUrl is set and will NOT be read.
351
+ if (winner !== full && !isNonEmptyString(rawUrl) && isNonEmptyString(full.serverUrl)) {
352
+ warn(`top-level serverUrl is set but ${winnerPath} takes precedence and has no valid serverUrl — ` +
353
+ `the plugin will NOT use the top-level value; move serverUrl into ${winnerPath}`);
354
+ }
355
+ return resolved;
356
+ }
357
+ // ---------------------------------------------------------------------------
259
358
  // Plugin export
260
359
  // ---------------------------------------------------------------------------
261
360
  exports.default = {
@@ -269,12 +368,19 @@ exports.default = {
269
368
  api.registerService({
270
369
  id: "hicortex-service",
271
370
  async start(ctx) {
272
- const config = (ctx.config ?? {});
371
+ // OC passes the ENTIRE openclaw.json as ctx.config (verified against
372
+ // gateway-cli createServiceContext: config = params.cfg), not a
373
+ // per-plugin section. resolveOcPluginConfig picks our section out of
374
+ // it (branch order documented on the function) and validates the
375
+ // scalar keys at the boundary. It never throws, so a malformed config
376
+ // can only degrade (warn + default), never half-initialize the plugin.
377
+ const config = resolveOcPluginConfig(ctx.config);
273
378
  pluginConfig = config;
274
379
  const log = ctx.logger
275
380
  ? (msg) => ctx.logger.info(msg)
276
381
  : console.log;
277
- // Resolve server URL and auth token from plugin config
382
+ // Resolve server URL and auth token from plugin config (both already
383
+ // validated strings — or absent, hence the ?? default)
278
384
  serverUrl = (config.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
279
385
  authToken = config.authToken;
280
386
  // Use stateDir from context so tests can redirect state writes
@@ -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.10.0",
5
+ "version": "0.19.1",
6
6
  "kind": "lifecycle",
7
7
  "skills": ["./skills/hicortex-memory"],
8
8
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Persistent agent identity for AI agents \u2014 a hand-edited identity layer, nightly-distilled experience, and lessons injected every session, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {