@gamaze/hicortex 0.19.6 → 0.20.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  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.
6
6
 
7
- Works with **Hermes**, **OpenClaw**, **Claude Code**, **Pi**, and any MCP-compatible agent.
7
+ Works with **Hermes**, **OpenClaw**, **Claude Code**, **Pi**, **opencode**, and any MCP-compatible agent.
8
8
 
9
9
  **Website:** [hicortex.gamaze.com](https://hicortex.gamaze.com) · **Docs:** [hicortex.gamaze.com/docs](https://hicortex.gamaze.com/docs/)
10
10
 
@@ -87,7 +87,7 @@ Bare top-level keys (`"serverUrl": …` at the root of `openclaw.json`) still wo
87
87
  | When | What | How |
88
88
  |------|------|-----|
89
89
  | Agent start | Standing identity (`## Identity`) + recent lessons fetched fresh and injected | CC SessionStart hook (calls `hicortex learnings-identity`; `lessons-context` kept as an alias so existing installed hooks don't break) / Hermes plugin `system_prompt_block` / OC `before_agent_start` hook |
90
- | Every prompt (0.14) | A compact **recall index** of relevant memories is injected — one line per memory; the agent lazy-loads full content with `hicortex_get` only when needed | All three harnesses call server `POST /recall-index` per turn: CC UserPromptSubmit hook (`hicortex recall-hook`), Hermes plugin `prefetch` (0.7.0; falls back to `/search` injection against a pre-0.14 server), OC `before_agent_start` hook (fires per inbound message). Turn-based dedup per session; resets on new session/compaction. Fail-soft |
90
+ | Every prompt (0.14) | A compact **recall index** of relevant memories is injected — one line per memory; the agent lazy-loads full content with `hicortex_get` only when needed | All five harnesses call server `POST /recall-index` per turn: CC UserPromptSubmit hook (`hicortex recall-hook`), Hermes plugin `prefetch` (0.7.0; falls back to `/search` injection against a pre-0.14 server), OC `before_agent_start` hook (fires per inbound message), Pi extension `before_agent_start` (0.20), opencode plugin messages-transform hook (0.21). Turn-based dedup per session; resets on new session/compaction. Fail-soft |
91
91
  | Nightly | Denoise sessions → POST /distill → server distills + embeds + stores → consolidate (score, reflect, link, decay) | Automatic pipeline — no manual steps |
92
92
 
93
93
  **Exposure vs use (0.14):** appearing in the recall index only marks a memory as *shown* (it stops decaying while topically active); fetching it with `hicortex_get` marks it as *used* (durable strengthening). Memory importance is driven by what agents actually use, not by what was pushed at them.
@@ -158,7 +158,7 @@ Beyond auto-distilled memories and lessons, Hicortex holds a **hand-edited ident
158
158
 
159
159
  - **Storage:** plain files on the server at `~/.hicortex/identity/*.md` — one file per section (recommended starter sections `user.md` + `rules.md`, which you create — nothing is pre-populated; add more by dropping in a file). It lives outside the memories table; consolidation never touches it.
160
160
  - **Edit:** the web editor at `http://localhost:8787/identity/ui` (one tab per section, Save), or the CLI `hicortex identity show [name]` / `hicortex identity edit <name>`.
161
- - **Delivery:** injected into the harnesses listed in `identityClients` (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc` — all three are supported since 0.13).
161
+ - **Delivery:** injected into the harnesses listed in `identityClients` (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc`/`pi`/`opencode` — CC/Hermes/OpenClaw since 0.13, Pi since 0.20, opencode since 0.21).
162
162
  - **Deletion** is filesystem-only — remove the file on the server (as the daemon user).
163
163
 
164
164
  ### Per-agent identity (0.13)
@@ -183,6 +183,7 @@ npx @gamaze/hicortex init # Set up server mode
183
183
  npx @gamaze/hicortex init --server <url> # Set up client mode
184
184
  npx @gamaze/hicortex nightly # Run distill + consolidate (full nightly)
185
185
  npx @gamaze/hicortex nightly --capture-only # Capture only, skip consolidation (safe for sub-daily runs)
186
+ npx @gamaze/hicortex nightly --evict-only # Memory-cap eviction only — pure DB, no capture, no LLM (idempotent; honors --dry-run)
186
187
  npx @gamaze/hicortex nightly --dry-run # Preview without changes
187
188
  npx @gamaze/hicortex classify-domains # Backfill domain tags over the corpus (see Memory Domains & Tags)
188
189
  npx @gamaze/hicortex classify-types # Reclassify memory types (episode/fact/decision)
@@ -239,16 +240,19 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
239
240
  | `llmBreakerCooldownMs` | How long the breaker stays open before one half-open trial call goes out (default 600000 = 10 min). A failing trial re-opens it; a succeeding one resets the counter. |
240
241
  | `llmProbeTimeoutMs` | Patience of the readiness probe — one minimal 1-token generation request the nightly sends before consolidating and the daemon sends before distilling (default 60000 = 1 min). Catches a gateway that answers health/model-list queries while generation is dead; a failed probe skips consolidation (`endpoint_down`, retried next run) and answers `/distill` with a 503 so capture holds its cursor. |
241
242
  | `llmProbeTtlMs` | How long the daemon caches a `/distill` probe outcome (default 300000 = 5 min). A healthy capture cadence pays at most one probe per window; a dead endpoint turns into fast cached 503s instead of every request paying the probe timeout. |
243
+ | `llmSingleFlight` | **Serialized LLM calls — default `true`.** At most ONE request in flight per endpoint at any moment, across every process (the daemon distilling concurrent captures, the nightly consolidating, CLI backfills take turns via a per-endpoint lock file). One Hicortex server is several callers at once, and local single-user model servers (a Mac mini or laptop serving one big-context model) can stall or crash — taking the machine with them — under two concurrent large requests. Queued calls wait their turn; batches run back-to-back. **Set `false` if your endpoint is a beefy multi-tenant service that parallelizes well and you want faster consolidation** — you opt into responsibility for the endpoint's concurrency safety. |
244
+ | `llmSingleFlightWaitMs` | How long a queued call waits for the in-flight call before failing as endpoint-down and retrying per the normal ladder/breaker rules (default: `max(900000, llmTimeoutMs)` — a waiter never gives up before a legitimate in-flight call's own, possibly raised, ceiling expires). Only meaningful with serialization on. Note under contention: the readiness probe queues too, but its wait is capped by its own probe budget — during a long consolidation batch, `/distill` answers 503 quickly (captures cursor-hold and retry, lossless) rather than holding the socket for the full queue budget. |
242
245
  | `authToken` | Bearer token for endpoint auth. Generated on first `init` in server mode. Find the active token with `hicortex status` or in `~/.hicortex/config.json`. |
243
246
  | `corsAllowedOrigins` | Browser origins allowed to read cross-origin responses, e.g. `["https://ui.example.com"]`. **Empty by default** — the server sends no `Access-Control-Allow-Origin` and never `Allow-Credentials`, so no external web page can read its data. The bundled `/viz` and `/identity/ui` pages are same-origin and need no entry. |
244
247
  | `licenseKey` | Commercial license key (optional; for display in `hicortex status`) |
245
248
  | `backupRetention` | How many of the newest backup artifacts (`hicortex-*.tar.gz`) the backup dir keeps after each successful write (default: 7; `0` keeps all). See [Backups](#backups) |
246
249
  | _env_ `HICORTEX_DISTILL_BODY_LIMIT_MB` | Environment override for the `/distill` body limit — **wins over the `distillBodyLimitMb` config key in every mode** (that is the point: a deployment operator pins it so tenant-writable config cannot raise it). Unset = config/default applies. |
250
+ | _env_ `HICORTEX_MEMORY_CAP` | Environment override for the memory soft cap — a **positive** value wins over the `memorySoftCap` config key in every mode; `0`/negative/malformed fall through to the config key, then the 10000 default (an env can pin a cap, never disable one — config `memorySoftCap: 0` still disables when the env is unset). Mode-agnostic operator knob: nightly eviction, the dashboard-snapshot capacity stamp, and the live dashboard gauge all resolve through the same resolver, so the enforced and displayed caps can never disagree. Drives `nightly --evict-only` the same way. |
247
251
  | `domains` | Your memory domain list (`[{name, description}]`). Scaffolded by `init`; edit freely — see [Memory Domains & Tags](#memory-domains--tags) |
248
252
  | `weakPrimaryFloor` | Minimum similarity for a no-fit memory to keep a weak domain association (default: 0.45) |
249
253
  | `moduleIndexTokenBudget` | Max tokens for domain index in lessons context (default: 500) |
250
254
  | `lessonsLimit` | Max lessons injected into an agent's session-start context (default: 10). Lessons are ranked per session by project/domain affinity + recency + strength + access, so each session sees its most-relevant slice. Lower = leaner system prompts. |
251
- | `identityClients` | Which harnesses inject the [identity layer](#identity-layer) at session start (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc`) |
255
+ | `identityClients` | Which harnesses inject the [identity layer](#identity-layer) at session start (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc`/`pi`/`opencode`) |
252
256
  | `identityAgents` | Per-agent identity modes (0.13): `{ "<id>": "override" \| "global" \| "off" }`. Absent + no `agents/<id>/` dir → every agent gets the global set. Boot-time (restart to apply) — see [Per-agent identity](#per-agent-identity-013) |
253
257
  | `agentName` | This install's per-agent identity id sent as `?agent=`. **Unset by default** (CC shares the global identity — no `?agent=` sent). Explicit opt-in via `init --agent-name <name>`; `init --agent-name ""` clears it. An empty/whitespace value equals unset |
254
258
  | `captureCooldownHours` | Success-cooldown (hours) for the **capture watchdog** (0.17). The capture timer polls every ~20 min; the watchdog captures only if more than this has elapsed since the last *successful* capture (`state.lastNightly`). Default `6` (≈4 captures/day). A failed preflight retries on the next poll (~20 min) — so a transient fire-instant network miss costs minutes, not a day (#239) |
package/dist/cli.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * nightly Run capture + consolidate (manual trigger)
9
9
  * nightly --capture-only Capture only, skip consolidation
10
10
  * nightly --consolidate-only Consolidate only, skip capture (hosted service)
11
+ * nightly --evict-only Memory-cap eviction only — pure DB, no LLM (#317)
11
12
  * nightly --status Show nightly pipeline health check
12
13
  * relink Resumable link-discovery pass over the entire corpus (issue #143)
13
14
  * dedup Cluster + merge near-duplicate memories (issue #100)
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * nightly Run capture + consolidate (manual trigger)
10
10
  * nightly --capture-only Capture only, skip consolidation
11
11
  * nightly --consolidate-only Consolidate only, skip capture (hosted service)
12
+ * nightly --evict-only Memory-cap eviction only — pure DB, no LLM (#317)
12
13
  * nightly --status Show nightly pipeline health check
13
14
  * relink Resumable link-discovery pass over the entire corpus (issue #143)
14
15
  * dedup Cluster + merge near-duplicate memories (issue #100)
@@ -71,11 +72,16 @@ switch (command) {
71
72
  const dryRun = args.includes("--dry-run");
72
73
  const captureOnly = args.includes("--capture-only");
73
74
  const consolidateOnly = args.includes("--consolidate-only");
75
+ const evictOnly = args.includes("--evict-only");
74
76
  const watchdog = args.includes("--watchdog");
75
77
  if (captureOnly && consolidateOnly) {
76
78
  console.error("[hicortex] nightly: --capture-only and --consolidate-only are mutually exclusive");
77
79
  process.exit(1);
78
80
  }
81
+ if (evictOnly && (captureOnly || consolidateOnly)) {
82
+ console.error("[hicortex] nightly: --evict-only is mutually exclusive with --capture-only and --consolidate-only");
83
+ process.exit(1);
84
+ }
79
85
  // Timestamp every log line. The nightly writes to a file (launchd /
80
86
  // systemd StandardOutput append) with NO per-line timestamp, which made
81
87
  // diagnosing capture gaps impossible (the #239 investigation couldn't
@@ -98,7 +104,7 @@ switch (command) {
98
104
  }
99
105
  }
100
106
  import("./nightly.js").then(({ runNightly }) => {
101
- runNightly({ dryRun, captureOnly, consolidateOnly, watchdog, recaptureWindowDays }).catch((err) => {
107
+ runNightly({ dryRun, captureOnly, consolidateOnly, evictOnly, watchdog, recaptureWindowDays }).catch((err) => {
102
108
  console.error("[hicortex] Nightly pipeline failed:", err);
103
109
  process.exit(1);
104
110
  });
@@ -355,6 +361,7 @@ Options:
355
361
  nightly --dry-run Preview without changes
356
362
  nightly --capture-only Capture only, skip consolidation (safe to run multiple times/day)
357
363
  nightly --consolidate-only Consolidate only, skip capture (hosted-service per-tenant runs)
364
+ nightly --evict-only Memory-cap eviction only — pure DB, no capture, no LLM (#317)
358
365
  nightly --recapture-window <days> Re-discover sessions quiet since <days> ago (one-shot #189 recovery)
359
366
  nightly --status Show nightly pipeline health
360
367
  relink --dry-run Discovery + counts only, zero writes, cursor untouched
@@ -285,6 +285,27 @@ export declare function stageDecayPrune(db: Database.Database, dryRun: boolean):
285
285
  * (recall top-k competes against the long tail). Override via `memorySoftCap`.
286
286
  */
287
287
  export declare const DEFAULT_MEMORY_SOFT_CAP = 10000;
288
+ /**
289
+ * Resolve the effective soft cap (#317): a positive finite HICORTEX_MEMORY_CAP
290
+ * env wins over the `memorySoftCap` config key, which wins over
291
+ * DEFAULT_MEMORY_SOFT_CAP. The env is the PRICING boundary — in the hosted
292
+ * stack the cap is a per-plan parameter the provider pins into the tenant .env
293
+ * (docker exec inherits container env, so the eviction path sees it), and the
294
+ * tenant's config.json is bind-mounted writable at /data, so config-only
295
+ * resolution would let a tenant raise or disable its own cap. Same
296
+ * env-wins-precedence pattern as resolveTokenCap (token-budget.ts) and
297
+ * resolveBodyLimitMb (mcp-server.ts).
298
+ *
299
+ * Deliberate asymmetry with resolveTokenCap: a MALFORMED/0/negative env falls
300
+ * through (an env can pin a cap but never disable one), while config 0 is
301
+ * still honored as the #245 opt-out (indefinite growth) when the env is unset
302
+ * — env unset → config → default is exactly the pre-#317 behavior, so a
303
+ * self-hosted install that never hears about the env sees no change. Pure —
304
+ * exported for tests; every consumer (nightly eviction + snapshot stamp,
305
+ * dashboard live headline) must route through this ONE function so the
306
+ * enforced cap and the displayed cap cannot disagree.
307
+ */
308
+ export declare function resolveMemorySoftCap(configVal: unknown): number;
288
309
  export declare function stageMemoryCapEviction(db: Database.Database, dryRun: boolean, cap: number): {
289
310
  cap: number;
290
311
  evicted: number;
@@ -50,12 +50,14 @@ exports.buildSupersessionPrompt = buildSupersessionPrompt;
50
50
  exports.parseSupersessionReply = parseSupersessionReply;
51
51
  exports.stageSupersession = stageSupersession;
52
52
  exports.stageDecayPrune = stageDecayPrune;
53
+ exports.resolveMemorySoftCap = resolveMemorySoftCap;
53
54
  exports.stageMemoryCapEviction = stageMemoryCapEviction;
54
55
  exports.runConsolidation = runConsolidation;
55
56
  exports.msUntilHour = msUntilHour;
56
57
  exports.scheduleConsolidation = scheduleConsolidation;
57
58
  const retrieval_js_1 = require("./retrieval.js");
58
59
  const storage = __importStar(require("./storage.js"));
60
+ const config_read_js_1 = require("./config-read.js");
59
61
  const prompts_js_1 = require("./prompts.js");
60
62
  const node_crypto_1 = require("node:crypto");
61
63
  const features_js_1 = require("./features.js");
@@ -291,11 +293,16 @@ function parseJsonLenient(text, fallback) {
291
293
  // ---------------------------------------------------------------------------
292
294
  // Stage 1: Pre-check
293
295
  // ---------------------------------------------------------------------------
294
- function readLastConsolidated() {
295
- return (0, state_js_1.loadState)().lastConsolidated ?? "";
296
+ function readLastConsolidated(stateDir) {
297
+ // stateDir-optional for backcompat; runConsolidation threads its own
298
+ // stateDir so the skip decision reads the SAME state the run writes
299
+ // (previously this read the ambient home — a split-brain where a test or
300
+ // CLI caller with an isolated stateDir decided "skip" from the operator's
301
+ // real ~/.hicortex watermark).
302
+ return (0, state_js_1.loadState)(stateDir).lastConsolidated ?? "";
296
303
  }
297
- function stagePrecheck(db) {
298
- const lastTs = readLastConsolidated();
304
+ function stagePrecheck(db, stateDir) {
305
+ const lastTs = readLastConsolidated(stateDir);
299
306
  const lastDt = lastTs || "1970-01-01T00:00:00.000Z";
300
307
  const newMemories = storage.getMemoriesSince(db, lastDt);
301
308
  if (newMemories.length === 0) {
@@ -1261,6 +1268,41 @@ function stageDecayPrune(db, dryRun) {
1261
1268
  * (recall top-k competes against the long tail). Override via `memorySoftCap`.
1262
1269
  */
1263
1270
  exports.DEFAULT_MEMORY_SOFT_CAP = 10000;
1271
+ /** Env override for the soft cap (#317). Hosted: provider-set via the tenant
1272
+ * .env, tenant-immutable at runtime (same posture as HICORTEX_TOKEN_CAP).
1273
+ * Self-hosted: an operator knob — mode-agnostic, never branches on
1274
+ * hostedMode. */
1275
+ const MEMORY_CAP_ENV = "HICORTEX_MEMORY_CAP";
1276
+ /**
1277
+ * Resolve the effective soft cap (#317): a positive finite HICORTEX_MEMORY_CAP
1278
+ * env wins over the `memorySoftCap` config key, which wins over
1279
+ * DEFAULT_MEMORY_SOFT_CAP. The env is the PRICING boundary — in the hosted
1280
+ * stack the cap is a per-plan parameter the provider pins into the tenant .env
1281
+ * (docker exec inherits container env, so the eviction path sees it), and the
1282
+ * tenant's config.json is bind-mounted writable at /data, so config-only
1283
+ * resolution would let a tenant raise or disable its own cap. Same
1284
+ * env-wins-precedence pattern as resolveTokenCap (token-budget.ts) and
1285
+ * resolveBodyLimitMb (mcp-server.ts).
1286
+ *
1287
+ * Deliberate asymmetry with resolveTokenCap: a MALFORMED/0/negative env falls
1288
+ * through (an env can pin a cap but never disable one), while config 0 is
1289
+ * still honored as the #245 opt-out (indefinite growth) when the env is unset
1290
+ * — env unset → config → default is exactly the pre-#317 behavior, so a
1291
+ * self-hosted install that never hears about the env sees no change. Pure —
1292
+ * exported for tests; every consumer (nightly eviction + snapshot stamp,
1293
+ * dashboard live headline) must route through this ONE function so the
1294
+ * enforced cap and the displayed cap cannot disagree.
1295
+ */
1296
+ function resolveMemorySoftCap(configVal) {
1297
+ const envCap = Number(process.env[MEMORY_CAP_ENV]);
1298
+ if (Number.isFinite(envCap) && envCap > 0)
1299
+ return envCap;
1300
+ // Reuse the disk→runtime boundary validator (warn-on-rejected-value) so the
1301
+ // config half behaves byte-for-byte like the pre-#317 readNonNegativeConfig
1302
+ // call sites: absent → default, valid non-negative (incl. 0 = disabled)
1303
+ // passes through, invalid → warn + default.
1304
+ return (0, config_read_js_1.readNonNegativeConfig)({ memorySoftCap: configVal }, "memorySoftCap", exports.DEFAULT_MEMORY_SOFT_CAP);
1305
+ }
1264
1306
  function stageMemoryCapEviction(db, dryRun, cap) {
1265
1307
  // `0` = explicitly disabled (current/legacy behaviour). The guard is on `<=`
1266
1308
  // not `===` to also absorb a stray negative (readNonNegativeConfig already
@@ -1335,7 +1377,7 @@ memorySoftCap) {
1335
1377
  stages: {},
1336
1378
  };
1337
1379
  // Stage 1: Pre-check
1338
- const precheck = stagePrecheck(db);
1380
+ const precheck = stagePrecheck(db, stateDir);
1339
1381
  // Also check for unscored memories
1340
1382
  const unscored = storage.getUnscoredMemories(db);
1341
1383
  const newIds = new Set(precheck.newMemories.map((m) => m.id));
@@ -1343,7 +1385,15 @@ memorySoftCap) {
1343
1385
  ...precheck.newMemories,
1344
1386
  ...unscored.filter((m) => !newIds.has(m.id)),
1345
1387
  ];
1346
- const skip = scoreMemories.length === 0;
1388
+ // #194 no-fit scope: untagged rows (domain IS NULL) stay in the
1389
+ // re-evaluation scope — the decay/re-attempt contract ("re-halves once per
1390
+ // run while still below the floor") is work even on a quiet night with
1391
+ // nothing new. The skip below gated on new+unscored only, which the tests
1392
+ // never caught because stagePrecheck used to read the AMBIENT (always
1393
+ // empty in the suite) state instead of the run's own watermark — threading
1394
+ // stateDir (#357) exposed the divergence between test and production.
1395
+ const nofitInScope = db.prepare("SELECT COUNT(*) AS n FROM memories WHERE domain IS NULL").get().n;
1396
+ const skip = scoreMemories.length === 0 && nofitInScope === 0;
1347
1397
  report.stages.precheck = {
1348
1398
  skip,
1349
1399
  reason: skip
@@ -1413,8 +1463,18 @@ memorySoftCap) {
1413
1463
  report.status = "failed";
1414
1464
  console.error("[hicortex] Consolidation pipeline error:", err);
1415
1465
  }
1416
- // Update last-consolidated timestamp
1417
- if (!dryRun && report.status === "completed") {
1466
+ // Update last-consolidated timestamp. #357: the stages fail SOFT, so a run
1467
+ // against an endpoint that died mid-way still reports status "completed"
1468
+ // here — advancing the timestamp made state.json disagree with the
1469
+ // nightly's breaker-open override to endpoint_down (observed 0.20.0 soak:
1470
+ // endpoint_down reported, lastConsolidated advanced anyway). Gate on the
1471
+ // SAME signal the nightly's override reads (llm.breakerOpen) so the two
1472
+ // sites agree for the breaker case — keep this condition and the override
1473
+ // in nightly.ts in sync if either ever grows. (Sibling soft-fail paths —
1474
+ // e.g. a sustained-429 run, which never accrues to the breaker because a
1475
+ // 429 proves the endpoint answers — still advance; that is the #337
1476
+ // taxonomy working as designed.)
1477
+ if (!dryRun && report.status === "completed" && !llm.breakerOpen) {
1418
1478
  (0, state_js_1.updateState)((s) => {
1419
1479
  s.lastConsolidated = new Date().toISOString();
1420
1480
  return s;
package/dist/dashboard.js CHANGED
@@ -361,7 +361,10 @@ function handleDashboardData(db, query, config) {
361
361
  // Headline = live corpus (the chart shows history; the headline shows now).
362
362
  // `memory_soft_cap` (#245): resolve from the live config (the source of
363
363
  // truth for "what cap is in force right now"), defaulting to the production
364
- // default. The page renders no gauge when it's 0 (disabled).
364
+ // default. The page renders no gauge when it's 0 (disabled). #317: routes
365
+ // through resolveMemorySoftCap so a HICORTEX_MEMORY_CAP env pin is honored
366
+ // here too — the DISPLAYED cap can never disagree with the ENFORCED cap
367
+ // (both consumers share the one resolver).
365
368
  //
366
369
  // `tokens` (#246): period accrual from state.json (the same single source
367
370
  // the throttle check reads, so the dashboard always agrees with the runtime
@@ -372,7 +375,7 @@ function handleDashboardData(db, query, config) {
372
375
  total_memories: live.totals.mem,
373
376
  uses_per_showing: live.adoption?.uses_per_showing ?? null,
374
377
  cold_count: live.adoption?.cold_count ?? 0,
375
- memory_soft_cap: (0, config_read_js_1.readNonNegativeConfig)(config ?? {}, "memorySoftCap", consolidate_js_1.DEFAULT_MEMORY_SOFT_CAP),
378
+ memory_soft_cap: (0, consolidate_js_1.resolveMemorySoftCap)(config?.memorySoftCap),
376
379
  tokens: {
377
380
  used: tokenState?.total ?? 0,
378
381
  cap: (0, config_read_js_1.readNonNegativeConfig)(config ?? {}, "llmTokensPerMonth", 0),
@@ -227,9 +227,9 @@ export declare const IDENTITY_SIZE_WARN_BYTES = 16384;
227
227
  /** Backcompat alias for the pre-rename name (#264). */
228
228
  export declare const CONTEXT_SIZE_WARN_BYTES = 16384;
229
229
  /** Harness names that may inject the identity layer. */
230
- export declare const KNOWN_IDENTITY_CLIENTS: readonly ["cc", "hermes", "oc"];
230
+ export declare const KNOWN_IDENTITY_CLIENTS: readonly ["cc", "hermes", "oc", "pi", "opencode"];
231
231
  /** Backcompat alias for the pre-rename name (#264). */
232
- export declare const KNOWN_CONTEXT_CLIENTS: readonly ["cc", "hermes", "oc"];
232
+ export declare const KNOWN_CONTEXT_CLIENTS: readonly ["cc", "hermes", "oc", "pi", "opencode"];
233
233
  export interface ResolvedIdentityClients {
234
234
  /** The resolved, de-duped list of known client names. */
235
235
  clients: string[];
@@ -240,7 +240,7 @@ export interface ResolvedIdentityClients {
240
240
  export type ResolvedContextClients = ResolvedIdentityClients;
241
241
  /**
242
242
  * Normalize the raw `identityClients` config value (spec §2):
243
- * - `"all"` (any case) → ["cc","hermes","oc"]
243
+ * - `"all"` (any case) → ["cc","hermes","oc","pi","opencode"]
244
244
  * - array → lowercase, keep known names (de-duped), collect dropped unknowns
245
245
  * - missing / non-array-non-"all" → default ["cc"]
246
246
  * The resolved list is echoed by GET /identity as `clients` so each harness's
@@ -467,12 +467,12 @@ exports.CONTEXT_SIZE_WARN_BYTES = exports.IDENTITY_SIZE_WARN_BYTES;
467
467
  // Config — identityClients normalization
468
468
  // ---------------------------------------------------------------------------
469
469
  /** Harness names that may inject the identity layer. */
470
- exports.KNOWN_IDENTITY_CLIENTS = ["cc", "hermes", "oc"];
470
+ exports.KNOWN_IDENTITY_CLIENTS = ["cc", "hermes", "oc", "pi", "opencode"];
471
471
  /** Backcompat alias for the pre-rename name (#264). */
472
472
  exports.KNOWN_CONTEXT_CLIENTS = exports.KNOWN_IDENTITY_CLIENTS;
473
473
  /**
474
474
  * Normalize the raw `identityClients` config value (spec §2):
475
- * - `"all"` (any case) → ["cc","hermes","oc"]
475
+ * - `"all"` (any case) → ["cc","hermes","oc","pi","opencode"]
476
476
  * - array → lowercase, keep known names (de-duped), collect dropped unknowns
477
477
  * - missing / non-array-non-"all" → default ["cc"]
478
478
  * The resolved list is echoed by GET /identity as `clients` so each harness's
package/dist/init.d.ts CHANGED
@@ -6,7 +6,9 @@
6
6
  * 2. Remote HC server (HICORTEX_SERVER_URL — any reachable host:port)
7
7
  * 3. OC plugin installed (~/.openclaw/openclaw.json)
8
8
  * 4. CC MCP already registered (~/.claude/settings.json)
9
- * 5. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
9
+ * 5. Hermes present (~/.hermes) / Pi present (~/.pi/agent) /
10
+ * opencode present (~/.config/opencode or ~/.local/share/opencode)
11
+ * 6. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
10
12
  *
11
13
  * Actions:
12
14
  * - Install persistent daemon (launchd/systemd)
@@ -23,6 +25,13 @@ import type { DomainDef } from "./types.js";
23
25
  * - "registered" — listed but connection not confirmed (server down / not restarted)
24
26
  */
25
27
  export declare function parseMcpListStatus(mcpListOutput: string): "connected" | "registered" | "missing";
28
+ /**
29
+ * The client config's serverUrl BEFORE this run re-points it — the value init
30
+ * last wrote into the Hermes plugin's config (so the plugin step can tell an
31
+ * init-owned URL from a user-set one). Exported for tests.
32
+ */
33
+ export declare function readPreviousServerUrl(configPath: string): string | undefined;
34
+ export declare function setupHermes(serverUrl: string, authToken: string, previousServerUrl?: string): void;
26
35
  /**
27
36
  * Parse a KEY=VALUE env file (e.g. ~/.hermes/.env or ~/.claude/settings.json env block).
28
37
  * Handles: comments (#), quoted values, empty lines.