@gamaze/hicortex 0.17.6 → 0.18.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 +30 -28
- package/assets/dashboard.html +121 -5
- package/assets/{context.html → identity.html} +18 -18
- package/assets/viz.html +19 -7
- package/dist/claude-md.d.ts +2 -1
- package/dist/claude-md.js +2 -1
- package/dist/cli-args.d.ts +9 -0
- package/dist/cli-args.js +16 -0
- package/dist/cli.js +29 -20
- package/dist/consolidate.d.ts +15 -0
- package/dist/consolidate.js +30 -3
- package/dist/dashboard.d.ts +58 -1
- package/dist/dashboard.js +27 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/health.d.ts +68 -0
- package/dist/health.js +73 -0
- package/dist/identity-cli.d.ts +90 -0
- package/dist/{context-cli.js → identity-cli.js} +66 -48
- package/dist/{context-store.d.ts → identity-store.d.ts} +94 -31
- package/dist/{context-store.js → identity-store.js} +212 -71
- package/dist/index.d.ts +12 -5
- package/dist/index.js +57 -29
- package/dist/init.d.ts +44 -8
- package/dist/init.js +142 -37
- package/dist/learnings-identity.d.ts +149 -0
- package/dist/{lessons-context.js → learnings-identity.js} +96 -52
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +217 -68
- package/dist/memory-instructions.d.ts +6 -6
- package/dist/memory-instructions.js +6 -6
- package/dist/nightly.js +65 -6
- package/dist/paths.js +1 -1
- package/dist/recall-hook-cli.d.ts +1 -1
- package/dist/recall-hook-cli.js +3 -3
- package/dist/recall-index.js +5 -2
- package/dist/status.d.ts +2 -2
- package/dist/status.js +11 -9
- package/dist/telemetry.d.ts +10 -0
- package/dist/type-classify.js +4 -1
- package/dist/type-labels.d.ts +30 -0
- package/dist/type-labels.js +43 -0
- package/dist/types.d.ts +28 -0
- package/dist/uninstall.d.ts +12 -0
- package/dist/uninstall.js +21 -3
- package/dist/viz.d.ts +24 -11
- package/dist/viz.js +97 -32
- package/hermes-plugin/hicortex/README.md +4 -2
- package/package.json +2 -2
- package/dist/context-cli.d.ts +0 -69
- package/dist/lessons-context.d.ts +0 -102
package/dist/viz.js
CHANGED
|
@@ -26,19 +26,20 @@
|
|
|
26
26
|
* construction. Public like the /viz shell (static public code, no data).
|
|
27
27
|
*/
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.VIZ_VENDOR_FILES = void 0;
|
|
29
|
+
exports.contextUiHandler = exports.readContextHtml = exports.resolveContextHtmlPath = exports.VIZ_VENDOR_FILES = void 0;
|
|
30
30
|
exports.createAuthMiddleware = createAuthMiddleware;
|
|
31
31
|
exports.resolveVizHtmlPath = resolveVizHtmlPath;
|
|
32
32
|
exports.readVizHtml = readVizHtml;
|
|
33
33
|
exports.vizHandler = vizHandler;
|
|
34
|
-
exports.
|
|
35
|
-
exports.
|
|
36
|
-
exports.
|
|
34
|
+
exports.resolveIdentityHtmlPath = resolveIdentityHtmlPath;
|
|
35
|
+
exports.readIdentityHtml = readIdentityHtml;
|
|
36
|
+
exports.identityUiHandler = identityUiHandler;
|
|
37
37
|
exports.resolveDashboardHtmlPath = resolveDashboardHtmlPath;
|
|
38
38
|
exports.readDashboardHtml = readDashboardHtml;
|
|
39
39
|
exports.dashboardHandler = dashboardHandler;
|
|
40
40
|
exports.resolveVizVendorPath = resolveVizVendorPath;
|
|
41
41
|
exports.vizVendorHandler = vizVendorHandler;
|
|
42
|
+
const node_crypto_1 = require("node:crypto");
|
|
42
43
|
const node_fs_1 = require("node:fs");
|
|
43
44
|
const node_path_1 = require("node:path");
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
@@ -58,14 +59,42 @@ exports.VIZ_VENDOR_FILES = new Set([
|
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
59
60
|
// Auth middleware
|
|
60
61
|
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Constant-time bearer comparison (#254). Compares the FULL `Authorization`
|
|
64
|
+
* header (not just the token portion) against the expected `Bearer <token>`
|
|
65
|
+
* form. This couples the scheme + token into one fixed-length secret, so a
|
|
66
|
+
* caller cannot learn anything about the token by varying the scheme prefix.
|
|
67
|
+
*
|
|
68
|
+
* `crypto.timingSafeEqual` THROWS RangeError on mismatched buffer lengths, so
|
|
69
|
+
* the length check MUST gate the call. The short-circuit on `a.length !==
|
|
70
|
+
* b.length` leaks only the header LENGTH — which is already visible on the
|
|
71
|
+
* wire (HTTP header sizes are not secret); the secret token bytes are never
|
|
72
|
+
* compared byte-by-byte through a timing side channel.
|
|
73
|
+
*/
|
|
74
|
+
function safeBearerMatch(headerValue, expectedToken) {
|
|
75
|
+
if (!headerValue)
|
|
76
|
+
return false;
|
|
77
|
+
const a = Buffer.from(headerValue);
|
|
78
|
+
const b = Buffer.from(`Bearer ${expectedToken}`);
|
|
79
|
+
return a.length === b.length && (0, node_crypto_1.timingSafeEqual)(a, b);
|
|
80
|
+
}
|
|
61
81
|
/**
|
|
62
82
|
* Bearer token auth — ALWAYS installed, fail-closed. /health, the /viz page
|
|
63
83
|
* shell, and localhost bypass (OPTIONS is short-circuited by the CORS
|
|
64
84
|
* middleware before this runs). With no token configured, remote requests are
|
|
65
85
|
* REJECTED (not open): the default bind is 0.0.0.0, so "no token = no auth"
|
|
66
86
|
* would expose the whole memory store to the network.
|
|
87
|
+
*
|
|
88
|
+
* `authTokenPrevious` (optional, #254) is the prior token kept around during
|
|
89
|
+
* rotation. Both tokens are accepted; this gives a zero-downtime rotation
|
|
90
|
+
* window — in-flight clients configured with the old token keep working until
|
|
91
|
+
* they pick up the new one. BOTH comparisons are constant-time and both are
|
|
92
|
+
* always evaluated (no short-circuit), so a caller cannot learn WHICH token
|
|
93
|
+
* matched from the response timing. Absent/empty `authTokenPrevious` behaves
|
|
94
|
+
* exactly as the single-token middleware always has.
|
|
67
95
|
*/
|
|
68
|
-
function createAuthMiddleware(authToken) {
|
|
96
|
+
function createAuthMiddleware(authToken, authTokenPrevious) {
|
|
97
|
+
const previous = authTokenPrevious && authTokenPrevious.length > 0 ? authTokenPrevious : undefined;
|
|
69
98
|
return (req, res, next) => {
|
|
70
99
|
if (req.path === "/health")
|
|
71
100
|
return next();
|
|
@@ -76,13 +105,15 @@ function createAuthMiddleware(authToken) {
|
|
|
76
105
|
// normal Authorization header on its data fetches.
|
|
77
106
|
if (req.method === "GET" && req.path === "/viz")
|
|
78
107
|
return next();
|
|
79
|
-
// The /
|
|
108
|
+
// The /identity/ui page SHELL is public for the same reason as /viz: a
|
|
80
109
|
// self-contained static editor page, no data and no secrets (it ships
|
|
81
|
-
// verbatim in the npm tarball). The standing-
|
|
82
|
-
// from GET/PUT /
|
|
110
|
+
// verbatim in the npm tarball). The standing-identity DATA it edits comes
|
|
111
|
+
// from GET/PUT /identity, which stay bearer-only (localhost bypass) like
|
|
83
112
|
// every other data route; the page collects the token client-side and
|
|
84
|
-
// sends it as a normal Authorization header on its /
|
|
85
|
-
|
|
113
|
+
// sends it as a normal Authorization header on its /identity fetches.
|
|
114
|
+
// #264 backcompat: the legacy /context/ui URL is also exempted (the alias
|
|
115
|
+
// route serves the same shell).
|
|
116
|
+
if (req.method === "GET" && (req.path === "/identity/ui" || req.path === "/context/ui"))
|
|
86
117
|
return next();
|
|
87
118
|
// The /dashboard page SHELL is public for the same reason as /viz and
|
|
88
119
|
// /context/ui: a self-contained view-only analytics page, no data and no
|
|
@@ -113,10 +144,20 @@ function createAuthMiddleware(authToken) {
|
|
|
113
144
|
const ip = req.ip ?? req.socket.remoteAddress ?? "";
|
|
114
145
|
if (ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1")
|
|
115
146
|
return next();
|
|
116
|
-
|
|
147
|
+
// Constant-time bearer check (#254). When authTokenPrevious is set, BOTH
|
|
148
|
+
// tokens are compared every request (no short-circuit) so timing cannot
|
|
149
|
+
// reveal which one matched. The OR of the two booleans is the accept
|
|
150
|
+
// signal — evaluated after both comparisons complete.
|
|
151
|
+
const header = req.headers.authorization;
|
|
152
|
+
const matchesCurrent = authToken ? safeBearerMatch(header, authToken) : false;
|
|
153
|
+
const matchesPrevious = previous ? safeBearerMatch(header, previous) : false;
|
|
154
|
+
if (matchesCurrent || matchesPrevious)
|
|
117
155
|
return next();
|
|
118
156
|
res.status(401).json({
|
|
119
|
-
|
|
157
|
+
// Gate on (authToken || previous): in the mixed-config edge case
|
|
158
|
+
// (authToken unset, authTokenPrevious set) the middleware still accepts
|
|
159
|
+
// the previous token — so "No auth token configured" would mislead.
|
|
160
|
+
error: (authToken || previous)
|
|
120
161
|
? "Unauthorized"
|
|
121
162
|
: "No auth token configured on this server — run `npx @gamaze/hicortex init` on the server, then connect with its token.",
|
|
122
163
|
});
|
|
@@ -154,54 +195,69 @@ function vizHandler() {
|
|
|
154
195
|
res.type("html").send(readVizHtml());
|
|
155
196
|
}
|
|
156
197
|
catch (err) {
|
|
157
|
-
|
|
198
|
+
// /viz is a PUBLIC, UNAUTHENTICATED route — the catch must NOT echo the
|
|
199
|
+
// filesystem path of the failed readFileSync to an anonymous remote
|
|
200
|
+
// caller (#253). Log full detail server-side, return a generic body.
|
|
201
|
+
const detail = err instanceof Error ? (err.stack ?? `${err.name}: ${err.message}`) : String(err);
|
|
202
|
+
console.error(`[hicortex] /viz: ${detail}`);
|
|
203
|
+
res.status(503).json({ error: "Asset unavailable" });
|
|
158
204
|
}
|
|
159
205
|
};
|
|
160
206
|
}
|
|
161
207
|
// ---------------------------------------------------------------------------
|
|
162
|
-
//
|
|
208
|
+
// Identity layer editor page (/identity/ui, 0.12 — spec 2026-07-12 §5; renamed
|
|
209
|
+
// from /context/ui in 0.18 #264)
|
|
163
210
|
// ---------------------------------------------------------------------------
|
|
164
211
|
/**
|
|
165
|
-
* Resolve the on-disk path of the
|
|
212
|
+
* Resolve the on-disk path of the identity-layer editor page. Throws (fail
|
|
166
213
|
* explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
|
|
167
214
|
* assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
|
|
168
215
|
* (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
|
|
169
216
|
*/
|
|
170
|
-
function
|
|
171
|
-
const candidates = [(0, node_path_1.join)(__dirname, "..", "assets", "
|
|
217
|
+
function resolveIdentityHtmlPath() {
|
|
218
|
+
const candidates = [(0, node_path_1.join)(__dirname, "..", "assets", "identity.html")];
|
|
172
219
|
for (const candidate of candidates) {
|
|
173
220
|
if ((0, node_fs_1.existsSync)(candidate))
|
|
174
221
|
return candidate;
|
|
175
222
|
}
|
|
176
|
-
throw new Error(`
|
|
223
|
+
throw new Error(`identity.html asset not found — looked in: ${candidates.join(", ")}. ` +
|
|
177
224
|
`The package install is incomplete (assets/ missing).`);
|
|
178
225
|
}
|
|
179
|
-
/** Read the
|
|
180
|
-
function
|
|
181
|
-
return (0, node_fs_1.readFileSync)(
|
|
226
|
+
/** Read the identity editor page. Read at request time so a reinstall is live. */
|
|
227
|
+
function readIdentityHtml() {
|
|
228
|
+
return (0, node_fs_1.readFileSync)(resolveIdentityHtmlPath(), "utf-8");
|
|
182
229
|
}
|
|
183
230
|
/**
|
|
184
|
-
* Express handler for GET /
|
|
185
|
-
* standing
|
|
186
|
-
* cannot be read, exactly like vizHandler.
|
|
231
|
+
* Express handler for GET /identity/ui — the PRIMARY edit surface for the
|
|
232
|
+
* standing identity layer. 503 with the usual {error} shape when the asset
|
|
233
|
+
* cannot be read, exactly like vizHandler. Also serves the legacy
|
|
234
|
+
* /context/ui URL (#264 backcompat).
|
|
187
235
|
*/
|
|
188
|
-
function
|
|
236
|
+
function identityUiHandler() {
|
|
189
237
|
return (_req, res) => {
|
|
190
238
|
try {
|
|
191
|
-
res.type("html").send(
|
|
239
|
+
res.type("html").send(readIdentityHtml());
|
|
192
240
|
}
|
|
193
241
|
catch (err) {
|
|
194
|
-
|
|
242
|
+
// /identity/ui is a PUBLIC, UNAUTHENTICATED route (#253) — same
|
|
243
|
+
// sanitisation as vizHandler: log detail server-side only.
|
|
244
|
+
const detail = err instanceof Error ? (err.stack ?? `${err.name}: ${err.message}`) : String(err);
|
|
245
|
+
console.error(`[hicortex] /identity/ui: ${detail}`);
|
|
246
|
+
res.status(503).json({ error: "Asset unavailable" });
|
|
195
247
|
}
|
|
196
248
|
};
|
|
197
249
|
}
|
|
250
|
+
/** Backcompat aliases (#264). */
|
|
251
|
+
exports.resolveContextHtmlPath = resolveIdentityHtmlPath;
|
|
252
|
+
exports.readContextHtml = readIdentityHtml;
|
|
253
|
+
exports.contextUiHandler = identityUiHandler;
|
|
198
254
|
// ---------------------------------------------------------------------------
|
|
199
255
|
// Dashboard page (/dashboard, #224 — view-only memory analytics)
|
|
200
256
|
// ---------------------------------------------------------------------------
|
|
201
257
|
/**
|
|
202
258
|
* Resolve the on-disk path of the dashboard page. Throws (fail explicitly)
|
|
203
259
|
* when the asset is missing — same contract as resolveVizHtmlPath and
|
|
204
|
-
*
|
|
260
|
+
* resolveIdentityHtmlPath. assets/ sits next to both dist/ and src/ (the
|
|
205
261
|
* sibling layout the other resolvers rely on).
|
|
206
262
|
*/
|
|
207
263
|
function resolveDashboardHtmlPath() {
|
|
@@ -220,7 +276,7 @@ function readDashboardHtml() {
|
|
|
220
276
|
/**
|
|
221
277
|
* Express handler for GET /dashboard — the view-only analytics page (#224).
|
|
222
278
|
* 503 with the usual {error} shape when the asset cannot be read, exactly like
|
|
223
|
-
* vizHandler and
|
|
279
|
+
* vizHandler and identityUiHandler. The page SHELL is public (exempted in
|
|
224
280
|
* createAuthMiddleware); all data comes from GET /dashboard/data (bearer-only).
|
|
225
281
|
*/
|
|
226
282
|
function dashboardHandler() {
|
|
@@ -229,7 +285,11 @@ function dashboardHandler() {
|
|
|
229
285
|
res.type("html").send(readDashboardHtml());
|
|
230
286
|
}
|
|
231
287
|
catch (err) {
|
|
232
|
-
|
|
288
|
+
// /dashboard is a PUBLIC, UNAUTHENTICATED route (#253) — same
|
|
289
|
+
// sanitisation as vizHandler: log detail server-side only.
|
|
290
|
+
const detail = err instanceof Error ? (err.stack ?? `${err.name}: ${err.message}`) : String(err);
|
|
291
|
+
console.error(`[hicortex] /dashboard: ${detail}`);
|
|
292
|
+
res.status(503).json({ error: "Asset unavailable" });
|
|
233
293
|
}
|
|
234
294
|
};
|
|
235
295
|
}
|
|
@@ -268,9 +328,14 @@ function vizVendorHandler() {
|
|
|
268
328
|
res.send(body);
|
|
269
329
|
}
|
|
270
330
|
catch (err) {
|
|
331
|
+
// /viz/vendor/* is a PUBLIC, UNAUTHENTICATED route (#253) — same
|
|
332
|
+
// sanitisation as the other public asset routes: log detail server-side
|
|
333
|
+
// only, return a generic body. The allowlisted filename is fine to echo
|
|
334
|
+
// (it came from the fixed VIZ_VENDOR_FILES table, not the filesystem).
|
|
335
|
+
const detail = err instanceof Error ? (err.stack ?? `${err.name}: ${err.message}`) : String(err);
|
|
336
|
+
console.error(`[hicortex] /viz/vendor/${file}: ${detail}`);
|
|
271
337
|
res.status(503).json({
|
|
272
|
-
error: `Vendor asset ${file}
|
|
273
|
-
`(assets/vendor/ missing): ${err instanceof Error ? err.message : String(err)}`,
|
|
338
|
+
error: `Vendor asset ${file} unavailable — the package install is incomplete (assets/vendor/ missing)`,
|
|
274
339
|
});
|
|
275
340
|
}
|
|
276
341
|
};
|
|
@@ -22,12 +22,14 @@ That's the whole surface. No `sync_turn`, no compaction/session-end capture —
|
|
|
22
22
|
|
|
23
23
|
### Pushed recall index (0.7.0, server ≥ 0.14)
|
|
24
24
|
|
|
25
|
-
Instead of injecting full memory content every turn, `prefetch` sends the user's message to the server's `POST /recall-index` and injects the returned **index block** verbatim — one line per memory (id, title, date), capped and relevance-gated server-side. The agent fetches full content with `hicortex_get(id)` only when a line is actually relevant; that fetch is what strengthens the memory (exposure ≠ use). All tuning knobs (`recallMaxItems`, `recallMinSimilarity`, `recallReshowTurns`, `recallMinPromptChars`, …) live in the **server** config — the plugin carries none. Dedup is turn-based and server-side per session; the plugin resets it at `initialize` (the Hermes `MemoryProvider` interface exposes no compaction signal, so a mid-session context rebuild cannot trigger a reset — the server's turn-based re-show window covers that gap). Against a pre-0.14 server (404) the plugin falls back to the 0.6.x `GET /search` full-content prefetch, fail-soft, re-probing the endpoint every 10 minutes so a later server upgrade is picked up without a gateway restart. The recall calls carry the profile's configured `default_project` (and `mission_domains`) and use a short dedicated timeout (1.5 s) so a slow server can never stall a turn. (`privacy_filter` is deprecated since 0.7.2 — the server ignores privacy; see [Configuration](#
|
|
25
|
+
Instead of injecting full memory content every turn, `prefetch` sends the user's message to the server's `POST /recall-index` and injects the returned **index block** verbatim — one line per memory (id, title, date), capped and relevance-gated server-side. The agent fetches full content with `hicortex_get(id)` only when a line is actually relevant; that fetch is what strengthens the memory (exposure ≠ use). All tuning knobs (`recallMaxItems`, `recallMinSimilarity`, `recallReshowTurns`, `recallMinPromptChars`, …) live in the **server** config — the plugin carries none. Dedup is turn-based and server-side per session; the plugin resets it at `initialize` (the Hermes `MemoryProvider` interface exposes no compaction signal, so a mid-session context rebuild cannot trigger a reset — the server's turn-based re-show window covers that gap). Against a pre-0.14 server (404) the plugin falls back to the 0.6.x `GET /search` full-content prefetch, fail-soft, re-probing the endpoint every 10 minutes so a later server upgrade is picked up without a gateway restart. The recall calls carry the profile's configured `default_project` (and `mission_domains`) and use a short dedicated timeout (1.5 s) so a slow server can never stall a turn. (`privacy_filter` is deprecated since 0.7.2 — the server ignores privacy; see [Configuration](#configure-activate).)
|
|
26
26
|
|
|
27
27
|
### Per-agent standing context (0.13)
|
|
28
28
|
|
|
29
29
|
`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.
|
|
30
30
|
|
|
31
|
+
> **Note (#264 rename):** the server-side layer was renamed Context → Identity in 0.18. The `/context` endpoint remains as an alias so this plugin keeps working unchanged; the heading is still rendered as `## Context` here and will switch to `## Identity` in a follow-up plugin release. No action needed.
|
|
32
|
+
|
|
31
33
|
The plugin sends its **profile name** as `?agent=`, resolved in this order:
|
|
32
34
|
|
|
33
35
|
1. `agent_name` in the plugin config (explicit override);
|
|
@@ -79,7 +81,7 @@ Run it once per profile if you use Hermes profiles. Hermes allows **one** extern
|
|
|
79
81
|
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:
|
|
80
82
|
|
|
81
83
|
```bash
|
|
82
|
-
export HICORTEX_AUTH_TOKEN=hctx-
|
|
84
|
+
export HICORTEX_AUTH_TOKEN=hctx-<your-token> # or your custom token
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
Env overrides: `HICORTEX_URL`, `HICORTEX_AUTH_TOKEN`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.18.1",
|
|
4
|
+
"description": "Persistent agent identity for AI agents — 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": {
|
|
7
7
|
"hicortex": "dist/cli.js"
|
package/dist/context-cli.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* context-cli — `hicortex context show|edit`, the secondary/headless edit
|
|
3
|
-
* surface for the standing context layer (spec 2026-07-12 §6). The Web UI
|
|
4
|
-
* (`/context/ui`) is primary; this exists for boxes without a browser.
|
|
5
|
-
*
|
|
6
|
-
* hicortex context show [name] GET /context → print all sections, or one
|
|
7
|
-
* hicortex context edit <name> GET section → $EDITOR → PUT if changed
|
|
8
|
-
*
|
|
9
|
-
* URL/token resolution mirrors lessons-context.ts:44-49 (client mode →
|
|
10
|
-
* config.serverUrl; server mode → http://127.0.0.1:<port>; token from
|
|
11
|
-
* config.authToken) — explicitly NOT the hardcoded 127.0.0.1:8787 of
|
|
12
|
-
* status.ts. Fails soft with a clear message + non-zero exit on any server
|
|
13
|
-
* error, distinguishing a down server from an HTTP error (esp. 404 = server
|
|
14
|
-
* too old / wrong endpoint), like the OC plugin's describeGetFailure.
|
|
15
|
-
*/
|
|
16
|
-
/** Thrown for any expected, user-facing failure. cli.ts prints .message + exits 1. */
|
|
17
|
-
export declare class ContextCliError extends Error {
|
|
18
|
-
}
|
|
19
|
-
export interface ContextServerTarget {
|
|
20
|
-
baseUrl: string;
|
|
21
|
-
authToken?: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Resolve the server URL + token from a parsed config object. Pure + exported
|
|
25
|
-
* so it is unit-testable without a live config. Follows lessons-context.ts.
|
|
26
|
-
*/
|
|
27
|
-
export declare function resolveContextTarget(config: Record<string, unknown>): ContextServerTarget;
|
|
28
|
-
/** Read ~/.hicortex/config.json (or $HICORTEX_HOME/config.json). Missing → {}. */
|
|
29
|
-
export declare function loadConfig(): Record<string, unknown>;
|
|
30
|
-
/** The Save decision: PUT only when the edited content differs. Pure + tested. */
|
|
31
|
-
export declare function sectionChanged(before: string, after: string): boolean;
|
|
32
|
-
export interface ContextGetResponse {
|
|
33
|
-
sections: Record<string, string>;
|
|
34
|
-
updated_at: string | null;
|
|
35
|
-
clients: string[];
|
|
36
|
-
/** Present only for an agent-scoped read (0.13). */
|
|
37
|
-
agent?: string;
|
|
38
|
-
mode?: string;
|
|
39
|
-
}
|
|
40
|
-
/** GET /context. Throws ContextCliError with a clear message on any failure. */
|
|
41
|
-
export declare function getContext(target: ContextServerTarget, agent?: string): Promise<ContextGetResponse>;
|
|
42
|
-
/** PUT one section. Throws ContextCliError with a clear message on any failure. */
|
|
43
|
-
export declare function putSection(target: ContextServerTarget, name: string, content: string, agent?: string): Promise<void>;
|
|
44
|
-
/** Readable rendering of every section + the resolved clients line (show, no name). */
|
|
45
|
-
export declare function formatAllSections(data: ContextGetResponse): string;
|
|
46
|
-
/** Raw markdown for one section (show <name>), or null if it does not exist. */
|
|
47
|
-
export declare function formatOneSection(data: ContextGetResponse, name: string): string | null;
|
|
48
|
-
/**
|
|
49
|
-
* Default editor spawn: opens `file` in the first available editor, blocking
|
|
50
|
-
* until it exits. Returns true if an editor ran, false if none was found.
|
|
51
|
-
* Injectable so tests never actually spawn an editor.
|
|
52
|
-
*/
|
|
53
|
-
export type EditorSpawn = (file: string) => boolean;
|
|
54
|
-
/**
|
|
55
|
-
* `edit <name>`: validate name (fast-fail; the server enforces too) → fetch
|
|
56
|
-
* current content → $EDITOR on a temp file → PUT only if changed. Temp file is
|
|
57
|
-
* always cleaned up. `spawn` is injectable for tests.
|
|
58
|
-
*/
|
|
59
|
-
export declare function runEdit(name: string, spawn?: EditorSpawn, agent?: string): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Split out a `--agent <id>` flag (anywhere in argv) from the positional args.
|
|
62
|
-
* The flag omitted → the global scope.
|
|
63
|
-
*/
|
|
64
|
-
export declare function extractAgentFlag(args: string[]): {
|
|
65
|
-
agent?: string;
|
|
66
|
-
rest: string[];
|
|
67
|
-
};
|
|
68
|
-
/** Dispatch for `hicortex context <sub>`. Throws ContextCliError on bad usage/failure. */
|
|
69
|
-
export declare function runContextCommand(args: string[]): Promise<void>;
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lessons-context — query-time context injection for the CC SessionStart hook.
|
|
3
|
-
*
|
|
4
|
-
* Replaces file-based injection (injectLessons / injectLessonsFromServer).
|
|
5
|
-
* Reads ~/.hicortex/config.json to find the server URL, then fetches TWO
|
|
6
|
-
* endpoints concurrently and prints a compact Markdown block to stdout so CC
|
|
7
|
-
* picks it up as session context:
|
|
8
|
-
*
|
|
9
|
-
* GET /context → the standing context layer (user info + rules; 0.12).
|
|
10
|
-
* Injected as a `## Context` block ONLY when this harness
|
|
11
|
-
* ("cc") is in the server-resolved `clients` list (self-gate).
|
|
12
|
-
* GET /lessons → episodic memory lessons + memory index, rendered as the
|
|
13
|
-
* existing `## Hicortex Memory` block.
|
|
14
|
-
*
|
|
15
|
-
* The two fetches run in Promise.all, each with its OWN 3 s timeout and
|
|
16
|
-
* INDEPENDENT fail-soft: a /context failure must never cost the lessons block,
|
|
17
|
-
* and vice versa. Sequential fetches would double worst-case SessionStart
|
|
18
|
-
* latency (~6 s) — see spec §7.
|
|
19
|
-
*
|
|
20
|
-
* Fail-soft by design: ANY failure (missing config, network error, non-2xx,
|
|
21
|
-
* parse error) results in silent exit-0. A broken hook must never block a
|
|
22
|
-
* CC session, and a broken /context fetch must never blank the whole output.
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* The GET /context response shape, shared by the CC hook and the OC plugin so
|
|
26
|
-
* their gating cannot drift. `agent`/`mode` are echoed by a 0.13 server whenever
|
|
27
|
-
* `?agent=` was sent (in EVERY mode); a pre-0.13 server omits them.
|
|
28
|
-
*/
|
|
29
|
-
export interface ContextResponse {
|
|
30
|
-
sections?: Record<string, string>;
|
|
31
|
-
updated_at?: string;
|
|
32
|
-
clients?: string[];
|
|
33
|
-
agent?: string;
|
|
34
|
-
mode?: string;
|
|
35
|
-
}
|
|
36
|
-
export interface ResolvedConfig {
|
|
37
|
-
serverUrl: string;
|
|
38
|
-
authToken: string | undefined;
|
|
39
|
-
home: string;
|
|
40
|
-
/** Per-agent context id sent as ?agent= (0.13); null → global (no param). */
|
|
41
|
-
agentName: string | null;
|
|
42
|
-
/** Max lessons to inject (config.lessonsLimit, default 10). */
|
|
43
|
-
lessonsLimit?: number;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Read ~/.hicortex/config.json and resolve the server URL + auth token, or
|
|
47
|
-
* null when there is no usable config (server not set up yet — fail soft).
|
|
48
|
-
* Exported for reuse by the recall-hook CLI (#192) so the two CC hooks can
|
|
49
|
-
* never resolve the server differently.
|
|
50
|
-
*/
|
|
51
|
-
export declare function resolveConfig(): ResolvedConfig | null;
|
|
52
|
-
/**
|
|
53
|
-
* Title-case a section name for its heading: split on `-`/`_`, capitalize each
|
|
54
|
-
* word ("user" → "User", "my_notes" → "My Notes").
|
|
55
|
-
* Exported so the OC plugin (index.ts) renders the `## Context` block
|
|
56
|
-
* identically to the CC hook rather than duplicating the logic.
|
|
57
|
-
*/
|
|
58
|
-
export declare function titleCaseSection(name: string): string;
|
|
59
|
-
/**
|
|
60
|
-
* Stable section ordering: `user` first, then `rules` (the seeded primary
|
|
61
|
-
* sections, spec §8), then every other section alphabetically. Server-side
|
|
62
|
-
* enumeration order (readdirSync) is FS-dependent, so we sort here for a
|
|
63
|
-
* deterministic injection block. Exported for reuse by the OC plugin.
|
|
64
|
-
*/
|
|
65
|
-
export declare function orderSectionNames(names: string[]): string[];
|
|
66
|
-
/**
|
|
67
|
-
* Render the `## Context` block from a resolved section map, or null when there
|
|
68
|
-
* is nothing to inject (no sections, or every section blank after trimming).
|
|
69
|
-
* Pure — no gating, no I/O. Shared verbatim by the CC hook and the OC plugin so
|
|
70
|
-
* both harnesses emit an identical block. Sections are ordered (user, rules,
|
|
71
|
-
* then alphabetical) and rendered under title-cased `###` headings.
|
|
72
|
-
*/
|
|
73
|
-
export declare function renderContextBlock(sections: Record<string, string>): string | null;
|
|
74
|
-
/**
|
|
75
|
-
* Gate a GET /context response and render the `## Context` block, or null when
|
|
76
|
-
* nothing should be injected: `harness` not in the server-resolved `clients`,
|
|
77
|
-
* an empty/blank section set, or — when `requireAgentEcho` — a response that
|
|
78
|
-
* does not echo `agent`. The SINGLE gate used by both CC and OC so the two can
|
|
79
|
-
* never drift (the Python Hermes plugin `provider.py::_context_block` mirrors
|
|
80
|
-
* this logic — keep them in sync).
|
|
81
|
-
*
|
|
82
|
-
* `requireAgentEcho` is the old-server guard, and it is the CALLER's decision:
|
|
83
|
-
* - OC passes `agentId !== null` — when it actually sent an id, a 0.12 server
|
|
84
|
-
* that ignores `?agent=` (200 global, no echo) must NOT leak global context
|
|
85
|
-
* into every persona; on a bare fetch (no id) the guard is off (amendment
|
|
86
|
-
* A2).
|
|
87
|
-
* - CC passes `false` ALWAYS and deliberately (see the call site): a thin CC
|
|
88
|
-
* client auto-upgrades via npx BEFORE the server does, so during the upgrade
|
|
89
|
-
* window it talks to a 0.12 server that cannot hold ANY per-agent config —
|
|
90
|
-
* global IS the operator's intended state there, and a guard would instead
|
|
91
|
-
* blank ALL context for every CC session in that window.
|
|
92
|
-
*/
|
|
93
|
-
export declare function gateAndRenderContext(data: ContextResponse, harness: string, opts: {
|
|
94
|
-
requireAgentEcho: boolean;
|
|
95
|
-
}): string | null;
|
|
96
|
-
/**
|
|
97
|
-
* Fetch context + lessons concurrently and return the combined Markdown block,
|
|
98
|
-
* or null when neither yields anything (nothing to inject; caller prints
|
|
99
|
-
* nothing and exits 0). The `## Context` block is prepended before the existing
|
|
100
|
-
* `## Hicortex Memory` block.
|
|
101
|
-
*/
|
|
102
|
-
export declare function fetchLessonsContext(): Promise<string | null>;
|