@gamaze/hicortex 0.20.3 → 0.20.5
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 +20 -1
- package/assets/dashboard.html +318 -8
- package/assets/identity.html +349 -8
- package/assets/viz.html +352 -8
- package/dist/backup.d.ts +12 -8
- package/dist/backup.js +13 -9
- package/dist/claude-desktop.d.ts +138 -0
- package/dist/claude-desktop.js +251 -0
- package/dist/cli.d.ts +7 -2
- package/dist/cli.js +84 -3
- package/dist/consolidate.d.ts +8 -1
- package/dist/consolidate.js +82 -2
- package/dist/dashboard.d.ts +17 -0
- package/dist/dashboard.js +32 -0
- package/dist/db.js +36 -0
- package/dist/dedup.d.ts +157 -25
- package/dist/dedup.js +376 -83
- package/dist/domain-classify.js +4 -2
- package/dist/index.js +7 -7
- package/dist/init.d.ts +4 -1
- package/dist/init.js +103 -1
- package/dist/llm.d.ts +19 -13
- package/dist/llm.js +25 -14
- package/dist/mcp-server.d.ts +6 -0
- package/dist/mcp-server.js +94 -13
- package/dist/mcp-stdio.d.ts +138 -0
- package/dist/mcp-stdio.js +313 -0
- package/dist/memory-instructions.d.ts +18 -0
- package/dist/memory-instructions.js +39 -2
- package/dist/nightly.js +14 -1
- package/dist/reconsolidation.d.ts +323 -0
- package/dist/reconsolidation.js +1226 -0
- package/dist/retrieval.d.ts +14 -0
- package/dist/retrieval.js +41 -3
- package/dist/state.d.ts +23 -1
- package/dist/storage.d.ts +25 -0
- package/dist/storage.js +49 -7
- package/dist/type-classify.js +4 -2
- package/dist/types.d.ts +188 -0
- package/hermes-plugin/hicortex/provider.py +29 -17
- package/opencode-plugin/hicortex/index.ts +7 -7
- package/package.json +4 -2
- package/pi-extension/hicortex/index.ts +7 -7
- package/server.json +44 -0
package/dist/index.js
CHANGED
|
@@ -1038,7 +1038,7 @@ exports.default = {
|
|
|
1038
1038
|
// -----------------------------------------------------------------------
|
|
1039
1039
|
api.registerTool((_ctx) => ({
|
|
1040
1040
|
name: "hicortex_search",
|
|
1041
|
-
description: "Search long-term memory
|
|
1041
|
+
description: "Search shared long-term memory (all agents, all sessions). CALL THIS BEFORE assuming, guessing, or asking the user about anything that may have come up before: prior decisions, preferences, project facts, people, hardware, past incidents. If you are about to write 'I don't have information about…', search first.",
|
|
1042
1042
|
parameters: {
|
|
1043
1043
|
type: "object",
|
|
1044
1044
|
properties: {
|
|
@@ -1068,7 +1068,7 @@ exports.default = {
|
|
|
1068
1068
|
}), { name: "hicortex_search" });
|
|
1069
1069
|
api.registerTool((_ctx) => ({
|
|
1070
1070
|
name: "hicortex_get",
|
|
1071
|
-
description: "Fetch ONE memory's full content by id — use this to lazy-load entries from the '## Memory recall (auto)' index or from search results whose snippet was not enough. Fetching a memory marks it as used (strengthens it), so fetch entries that could change your action — not every shown one. When the memory shapes your answer, cite it
|
|
1071
|
+
description: "Fetch ONE memory's full content by id — use this to lazy-load entries from the '## Memory recall (auto)' index or from search results whose snippet was not enough. Fetching a memory marks it as used (strengthens it), so fetch entries that could change your action — not every shown one. When the memory shapes your answer, cite it to the user (id + date + origin agent) — mark a fetched memory `FETCHED` and a one-line entry cited unread `SNIPPET`; don't pass SNIPPET off as established.",
|
|
1072
1072
|
parameters: {
|
|
1073
1073
|
type: "object",
|
|
1074
1074
|
properties: {
|
|
@@ -1101,7 +1101,7 @@ exports.default = {
|
|
|
1101
1101
|
}), { name: "hicortex_get" });
|
|
1102
1102
|
api.registerTool((_ctx) => ({
|
|
1103
1103
|
name: "hicortex_recent",
|
|
1104
|
-
description: "Get recent memories, optionally filtered by project.
|
|
1104
|
+
description: "Get recent memories, optionally filtered by project. CALL THIS AT THE START of substantive work on a project to catch up on its latest state — cheaper than asking the user what happened.",
|
|
1105
1105
|
parameters: {
|
|
1106
1106
|
type: "object",
|
|
1107
1107
|
properties: {
|
|
@@ -1130,7 +1130,7 @@ exports.default = {
|
|
|
1130
1130
|
}), { name: "hicortex_recent" });
|
|
1131
1131
|
api.registerTool((_ctx) => ({
|
|
1132
1132
|
name: "hicortex_ingest",
|
|
1133
|
-
description: "Store a new memory in long-term storage. Use for Knowledge, Decisions, or Learnings.",
|
|
1133
|
+
description: "Store a new memory in long-term storage. Use for Knowledge, Decisions, or Learnings. Capture is automatic (nightly) — use this ONLY for explicitly requested learnings, never routine content.",
|
|
1134
1134
|
parameters: {
|
|
1135
1135
|
type: "object",
|
|
1136
1136
|
properties: {
|
|
@@ -1165,7 +1165,7 @@ exports.default = {
|
|
|
1165
1165
|
}), { name: "hicortex_ingest" });
|
|
1166
1166
|
api.registerTool((_ctx) => ({
|
|
1167
1167
|
name: "hicortex_lessons",
|
|
1168
|
-
description: "Get actionable Learnings
|
|
1168
|
+
description: "Get actionable Learnings — auto-generated insights about mistakes to avoid. CALL THIS before retrying an approach that failed before, or when picking up work where past problems may have been recorded.",
|
|
1169
1169
|
parameters: {
|
|
1170
1170
|
type: "object",
|
|
1171
1171
|
properties: {
|
|
@@ -1191,7 +1191,7 @@ exports.default = {
|
|
|
1191
1191
|
}), { name: "hicortex_lessons" });
|
|
1192
1192
|
api.registerTool((_ctx) => ({
|
|
1193
1193
|
name: "hicortex_index",
|
|
1194
|
-
description: "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain.",
|
|
1194
|
+
description: "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain. Call before a broad search to see which knowledge domains exist, or when unsure what the memory covers.",
|
|
1195
1195
|
parameters: {
|
|
1196
1196
|
type: "object",
|
|
1197
1197
|
properties: {},
|
|
@@ -1210,7 +1210,7 @@ exports.default = {
|
|
|
1210
1210
|
}), { name: "hicortex_index" });
|
|
1211
1211
|
api.registerTool((_ctx) => ({
|
|
1212
1212
|
name: "hicortex_graph",
|
|
1213
|
-
description: "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories.",
|
|
1213
|
+
description: "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories. Use it to explore memories connected to one you just fetched, or to find hub memories in a domain.",
|
|
1214
1214
|
parameters: {
|
|
1215
1215
|
type: "object",
|
|
1216
1216
|
properties: {
|
package/dist/init.d.ts
CHANGED
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
* 3. OC plugin installed (~/.openclaw/openclaw.json)
|
|
8
8
|
* 4. CC MCP already registered (~/.claude/settings.json)
|
|
9
9
|
* 5. Hermes present (~/.hermes) / Pi present (~/.pi/agent) /
|
|
10
|
-
* opencode present (~/.config/opencode or ~/.local/share/opencode)
|
|
10
|
+
* opencode present (~/.config/opencode or ~/.local/share/opencode) /
|
|
11
|
+
* Claude Desktop present (macOS ~/Library/Application Support/Claude,
|
|
12
|
+
* Windows %APPDATA%\Claude)
|
|
11
13
|
* 6. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
|
|
12
14
|
*
|
|
13
15
|
* Actions:
|
|
14
16
|
* - Install persistent daemon (launchd/systemd)
|
|
15
17
|
* - Register MCP server in CC settings
|
|
16
18
|
* - Install CC SessionStart hook for query-time lessons
|
|
19
|
+
* - Offer the Claude Desktop stdio MCP entry (opt-in, #381)
|
|
17
20
|
* - Strip old static CLAUDE.md learnings block if present
|
|
18
21
|
* - Remove legacy pre-0.10 CC commands (/learn, /hicortex-activate) if present
|
|
19
22
|
*/
|
package/dist/init.js
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* 3. OC plugin installed (~/.openclaw/openclaw.json)
|
|
9
9
|
* 4. CC MCP already registered (~/.claude/settings.json)
|
|
10
10
|
* 5. Hermes present (~/.hermes) / Pi present (~/.pi/agent) /
|
|
11
|
-
* opencode present (~/.config/opencode or ~/.local/share/opencode)
|
|
11
|
+
* opencode present (~/.config/opencode or ~/.local/share/opencode) /
|
|
12
|
+
* Claude Desktop present (macOS ~/Library/Application Support/Claude,
|
|
13
|
+
* Windows %APPDATA%\Claude)
|
|
12
14
|
* 6. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
|
|
13
15
|
*
|
|
14
16
|
* Actions:
|
|
15
17
|
* - Install persistent daemon (launchd/systemd)
|
|
16
18
|
* - Register MCP server in CC settings
|
|
17
19
|
* - Install CC SessionStart hook for query-time lessons
|
|
20
|
+
* - Offer the Claude Desktop stdio MCP entry (opt-in, #381)
|
|
18
21
|
* - Strip old static CLAUDE.md learnings block if present
|
|
19
22
|
* - Remove legacy pre-0.10 CC commands (/learn, /hicortex-activate) if present
|
|
20
23
|
*/
|
|
@@ -59,6 +62,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
59
62
|
const node_readline_1 = require("node:readline");
|
|
60
63
|
const node_crypto_1 = require("node:crypto");
|
|
61
64
|
const claude_md_js_1 = require("./claude-md.js");
|
|
65
|
+
const claude_desktop_js_1 = require("./claude-desktop.js");
|
|
62
66
|
const config_read_js_1 = require("./config-read.js");
|
|
63
67
|
const identity_store_js_1 = require("./identity-store.js");
|
|
64
68
|
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
@@ -99,6 +103,7 @@ async function detect() {
|
|
|
99
103
|
hermesFound: false,
|
|
100
104
|
piFound: false,
|
|
101
105
|
opencodeFound: false,
|
|
106
|
+
desktopFound: false,
|
|
102
107
|
existingDb: false,
|
|
103
108
|
};
|
|
104
109
|
// Check local server. /health/detail carries the diagnostics (memories,
|
|
@@ -143,6 +148,15 @@ async function detect() {
|
|
|
143
148
|
// Check opencode (~/.config/opencode or ~/.local/share/opencode — its
|
|
144
149
|
// global plugins dir / session store; it auto-loads ~/.config/opencode/plugins/)
|
|
145
150
|
result.opencodeFound = (0, node_fs_1.existsSync)(OPENCODE_CONFIG_DIR) || (0, node_fs_1.existsSync)(OPENCODE_DATA_DIR);
|
|
151
|
+
// Check Claude Desktop (#381 — macOS ~/Library/Application Support/Claude,
|
|
152
|
+
// Windows %APPDATA%\Claude; Linux has no Desktop build → null → skip
|
|
153
|
+
// silently). Detection is the DIRECTORY: the config file may not exist
|
|
154
|
+
// yet, and creating it fresh is exactly what the Desktop setup step does.
|
|
155
|
+
const desktopDir = (0, claude_desktop_js_1.desktopConfigDir)();
|
|
156
|
+
if (desktopDir && (0, node_fs_1.existsSync)(desktopDir)) {
|
|
157
|
+
result.desktopFound = true;
|
|
158
|
+
result.desktopDir = desktopDir;
|
|
159
|
+
}
|
|
146
160
|
// Check OC plugin
|
|
147
161
|
try {
|
|
148
162
|
const raw = (0, node_fs_1.readFileSync)(OC_CONFIG, "utf-8");
|
|
@@ -399,6 +413,84 @@ function setupOpencode() {
|
|
|
399
413
|
console.log(" → Restart opencode sessions to load the plugin (recall, identity, lessons, 9 tools)");
|
|
400
414
|
}
|
|
401
415
|
// ---------------------------------------------------------------------------
|
|
416
|
+
// Claude Desktop setup
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
/**
|
|
419
|
+
* Offer to add the Hicortex stdio MCP entry to Claude Desktop's
|
|
420
|
+
* claude_desktop_config.json (#381) — Desktop's only stdio MCP route, and a
|
|
421
|
+
* file owned by ANOTHER app, which is why this step is OPT-IN (default No,
|
|
422
|
+
* its own prompt after the batch actions) while the other harness setups
|
|
423
|
+
* just run. The write itself is merge-safe + atomic + backed up (see
|
|
424
|
+
* claude-desktop.ts); a malformed existing config is refused UNTOUCHED with
|
|
425
|
+
* the hand-fix snippet printed. The entry is the `hicortex mcp` bridge with
|
|
426
|
+
* an ABSOLUTE npx path (GUI apps don't inherit the shell PATH — a bare
|
|
427
|
+
* "npx" is the #1 Desktop failure mode); remote targets carry the server
|
|
428
|
+
* URL + token in env, loopback needs none (the bridge autostarts the daemon
|
|
429
|
+
* and loopback bypasses auth). Never throws — every failure path is a
|
|
430
|
+
* printed warning and init continues. No-ops when Desktop is not installed.
|
|
431
|
+
*/
|
|
432
|
+
async function setupClaudeDesktop(serverUrl, authToken) {
|
|
433
|
+
const dir = (0, claude_desktop_js_1.desktopConfigDir)();
|
|
434
|
+
if (!dir || !(0, node_fs_1.existsSync)(dir))
|
|
435
|
+
return; // no Claude Desktop on this machine
|
|
436
|
+
// Same non-interactive guard as persistLlmConfig: a piped/scripted init
|
|
437
|
+
// must not auto-apply an opt-in choice — a readline EOF would read as "".
|
|
438
|
+
if (!process.stdin.isTTY) {
|
|
439
|
+
console.log(" ⚠ Non-interactive stdin — Claude Desktop setup skipped; re-run `hicortex init` interactively to add it.");
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const answer = (await ask("Add the Hicortex MCP server to Claude Desktop? [y/N] ")).toLowerCase();
|
|
443
|
+
if (answer !== "y" && answer !== "yes") {
|
|
444
|
+
console.log(" → Claude Desktop left unchanged");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const configPath = (0, node_path_1.join)(dir, "claude_desktop_config.json");
|
|
448
|
+
// Absolute npx, rejecting npm's ephemeral /_npx/ cache (#176). Null → we
|
|
449
|
+
// cannot write a WORKING entry, so we print it and write nothing.
|
|
450
|
+
const npxPath = (0, claude_desktop_js_1.resolveDesktopNpxPath)();
|
|
451
|
+
if (!npxPath) {
|
|
452
|
+
console.log(" ⚠ Could not locate a durable npx on this machine — nothing written.");
|
|
453
|
+
printDesktopManualEntry(configPath, "<path-to-npx>", serverUrl, authToken);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// Remote targets carry the connection in env; loopback needs none.
|
|
457
|
+
const env = (0, claude_desktop_js_1.isLocalServerUrl)(serverUrl)
|
|
458
|
+
? undefined
|
|
459
|
+
: { HICORTEX_SERVER_URL: serverUrl, ...(authToken ? { HICORTEX_AUTH_TOKEN: authToken } : {}) };
|
|
460
|
+
// getPackageSpec() reads the config.json this run has already written (or
|
|
461
|
+
// an earlier run's), so the Desktop entry honours updateChannel exactly
|
|
462
|
+
// like the daemon/timer ExecStart does.
|
|
463
|
+
const entry = (0, claude_desktop_js_1.buildDesktopServerEntry)(npxPath, getPackageSpec(), env);
|
|
464
|
+
const result = (0, claude_desktop_js_1.writeDesktopServerConfig)(configPath, entry);
|
|
465
|
+
if (result.status === "written") {
|
|
466
|
+
console.log(` ✓ Added the Hicortex MCP server to Claude Desktop (${configPath})`);
|
|
467
|
+
if (result.backupPath) {
|
|
468
|
+
console.log(` Backup of the previous config: ${result.backupPath}`);
|
|
469
|
+
}
|
|
470
|
+
console.log(" → Fully quit and restart Claude Desktop to load it (Cmd+Q on macOS).");
|
|
471
|
+
}
|
|
472
|
+
else if (result.status === "refused") {
|
|
473
|
+
console.log(` ✗ ${result.reason}`);
|
|
474
|
+
printDesktopManualEntry(configPath, npxPath, serverUrl, authToken);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
console.log(` ⚠ Claude Desktop config write failed: ${result.reason} — nothing was changed.`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* The hand-fix snippet for the two paths where init writes nothing (no
|
|
482
|
+
* durable npx found / existing config refused): the ready-to-paste entry and
|
|
483
|
+
* the file it goes in, so the user can finish by hand in one paste.
|
|
484
|
+
*/
|
|
485
|
+
function printDesktopManualEntry(configPath, command, serverUrl, authToken) {
|
|
486
|
+
const env = (0, claude_desktop_js_1.isLocalServerUrl)(serverUrl)
|
|
487
|
+
? undefined
|
|
488
|
+
: { HICORTEX_SERVER_URL: serverUrl, ...(authToken ? { HICORTEX_AUTH_TOKEN: authToken } : {}) };
|
|
489
|
+
const entry = (0, claude_desktop_js_1.buildDesktopServerEntry)(command, getPackageSpec(), env);
|
|
490
|
+
console.log(` Add it by hand in ${configPath}, under "mcpServers":`);
|
|
491
|
+
console.log(` "hicortex": ${JSON.stringify(entry)}`);
|
|
492
|
+
}
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
402
494
|
// Hermes setup
|
|
403
495
|
// ---------------------------------------------------------------------------
|
|
404
496
|
/**
|
|
@@ -1662,6 +1754,8 @@ async function runInit(options = {}) {
|
|
|
1662
1754
|
console.log(` • Pi found at ${PI_AGENT_DIR}`);
|
|
1663
1755
|
if (d.opencodeFound)
|
|
1664
1756
|
console.log(` • opencode found at ${OPENCODE_CONFIG_DIR}`);
|
|
1757
|
+
if (d.desktopFound)
|
|
1758
|
+
console.log(` • Claude Desktop found at ${d.desktopDir}`);
|
|
1665
1759
|
if (d.ccMcpRegistered)
|
|
1666
1760
|
console.log(" • CC MCP already registered");
|
|
1667
1761
|
if (d.existingDb)
|
|
@@ -1822,6 +1916,10 @@ async function runInit(options = {}) {
|
|
|
1822
1916
|
if (d.opencodeFound) {
|
|
1823
1917
|
setupOpencode();
|
|
1824
1918
|
}
|
|
1919
|
+
// Offer the Claude Desktop MCP entry (opt-in — Desktop's config file
|
|
1920
|
+
// belongs to another app, so it gets its own prompt after the batch
|
|
1921
|
+
// actions above). No-ops when Desktop is not installed.
|
|
1922
|
+
await setupClaudeDesktop(serverUrl, authToken);
|
|
1825
1923
|
// Install CC SessionStart hook for query-time lesson injection.
|
|
1826
1924
|
// Lessons are now fetched live at session start — no static CLAUDE.md block needed.
|
|
1827
1925
|
installSessionStartHook();
|
|
@@ -2014,6 +2112,10 @@ async function runClientInit(serverUrl, agentName) {
|
|
|
2014
2112
|
console.log("\nopencode detected — installing plugin...");
|
|
2015
2113
|
setupOpencode();
|
|
2016
2114
|
}
|
|
2115
|
+
// Step 8d: Offer the Claude Desktop MCP entry (opt-in; no-ops when Desktop
|
|
2116
|
+
// is not installed). The client config was written in Step 3, so the
|
|
2117
|
+
// entry's package spec already honours updateChannel.
|
|
2118
|
+
await setupClaudeDesktop(serverUrl, authToken);
|
|
2017
2119
|
console.log("\n✓ Hicortex client setup complete!\n");
|
|
2018
2120
|
// Telemetry disclosure at install time (informed consent, best practice):
|
|
2019
2121
|
// opt-out telemetry is only acceptable if the user is TOLD about it.
|
package/dist/llm.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface LlmConfig {
|
|
|
23
23
|
provider: string;
|
|
24
24
|
/** Max output tokens for all phases (one model). Default 8192. */
|
|
25
25
|
maxTokens?: number;
|
|
26
|
+
/** Max output tokens for the CLASSIFY tier only — the short JSON-verdict
|
|
27
|
+
* calls (correction/supersession verdicts, rewrite contracts, type + tag
|
|
28
|
+
* classification). Default 1024. See HicortexConfig.classifyMaxTokens (#391). */
|
|
29
|
+
classifyMaxTokens?: number;
|
|
26
30
|
/** Toggle thinking on the openai-compat path for all phases. Absent = no kwarg sent.
|
|
27
31
|
* LOCAL-endpoint only (ollama / mlx-lm gateway); see HicortexConfig.enableThinking. */
|
|
28
32
|
enableThinking?: boolean;
|
|
@@ -82,18 +86,19 @@ export declare function resolveExplicitLlmConfig(overrides?: {
|
|
|
82
86
|
export declare const resolveLlmConfigForCC: typeof resolveExplicitLlmConfig;
|
|
83
87
|
/**
|
|
84
88
|
* Validate + copy the tuning keys (#220: maxTokens + enableThinking + numCtx +
|
|
85
|
-
* ollama flush) from the saved disk config onto a
|
|
86
|
-
* BOTH LlmConfig construction sites — the daemon
|
|
87
|
-
* distill) AND resolveSavedLlmConfig below (the nightly
|
|
88
|
-
* classify) — so every process honors the keys, and a future
|
|
89
|
-
* inherits them by construction.
|
|
89
|
+
* ollama flush; #391: classifyMaxTokens) from the saved disk config onto a
|
|
90
|
+
* runtime LlmConfig. Called by BOTH LlmConfig construction sites — the daemon
|
|
91
|
+
* in mcp-server.ts (runs distill) AND resolveSavedLlmConfig below (the nightly
|
|
92
|
+
* runs reflect + classify) — so every process honors the keys, and a future
|
|
93
|
+
* site calling this inherits them by construction.
|
|
90
94
|
*
|
|
91
|
-
* All keys are optional; absent = call-site defaults (maxTokens 8192,
|
|
92
|
-
* 8192, thinking kwarg omitted, flush off).
|
|
93
|
-
* dropped (readPositiveConfig /
|
|
94
|
-
* notably a JSON slip
|
|
95
|
-
*
|
|
96
|
-
* key exists to
|
|
95
|
+
* All keys are optional; absent = call-site defaults (maxTokens 8192,
|
|
96
|
+
* classifyMaxTokens 1024, numCtx 8192, thinking kwarg omitted, flush off).
|
|
97
|
+
* Wrong-typed values warn and are dropped (readPositiveConfig /
|
|
98
|
+
* readStrictBoolean / readNonNegativeConfig) — notably a JSON slip
|
|
99
|
+
* `"enableThinking": "false"` (string) is rejected rather than coerced to
|
|
100
|
+
* truthy thinking-on, which would silently invert the fix this key exists to
|
|
101
|
+
* apply.
|
|
97
102
|
*/
|
|
98
103
|
export declare function applyTierTuningOverlay(llmConfig: LlmConfig, savedConfig: Record<string, unknown> | null | undefined): void;
|
|
99
104
|
/**
|
|
@@ -213,8 +218,9 @@ export declare class LlmClient {
|
|
|
213
218
|
*/
|
|
214
219
|
completeDistill(prompt: string, maxTokens?: number): Promise<LlmResult>;
|
|
215
220
|
/**
|
|
216
|
-
* Classification-tier completion (
|
|
217
|
-
* serves all phases (#231) — thin wrapper kept for call-site
|
|
221
|
+
* Classification-tier completion (verdicts + tag/type classification). One
|
|
222
|
+
* model serves all phases (#231) — thin wrapper kept for call-site
|
|
223
|
+
* readability, but the tier keeps its OWN output ceiling (#391).
|
|
218
224
|
*/
|
|
219
225
|
completeClassify(prompt: string, maxTokens?: number): Promise<LlmResult>;
|
|
220
226
|
/**
|
package/dist/llm.js
CHANGED
|
@@ -87,18 +87,19 @@ function resolveExplicitLlmConfig(overrides) {
|
|
|
87
87
|
exports.resolveLlmConfigForCC = resolveExplicitLlmConfig;
|
|
88
88
|
/**
|
|
89
89
|
* Validate + copy the tuning keys (#220: maxTokens + enableThinking + numCtx +
|
|
90
|
-
* ollama flush) from the saved disk config onto a
|
|
91
|
-
* BOTH LlmConfig construction sites — the daemon
|
|
92
|
-
* distill) AND resolveSavedLlmConfig below (the nightly
|
|
93
|
-
* classify) — so every process honors the keys, and a future
|
|
94
|
-
* inherits them by construction.
|
|
90
|
+
* ollama flush; #391: classifyMaxTokens) from the saved disk config onto a
|
|
91
|
+
* runtime LlmConfig. Called by BOTH LlmConfig construction sites — the daemon
|
|
92
|
+
* in mcp-server.ts (runs distill) AND resolveSavedLlmConfig below (the nightly
|
|
93
|
+
* runs reflect + classify) — so every process honors the keys, and a future
|
|
94
|
+
* site calling this inherits them by construction.
|
|
95
95
|
*
|
|
96
|
-
* All keys are optional; absent = call-site defaults (maxTokens 8192,
|
|
97
|
-
* 8192, thinking kwarg omitted, flush off).
|
|
98
|
-
* dropped (readPositiveConfig /
|
|
99
|
-
* notably a JSON slip
|
|
100
|
-
*
|
|
101
|
-
* key exists to
|
|
96
|
+
* All keys are optional; absent = call-site defaults (maxTokens 8192,
|
|
97
|
+
* classifyMaxTokens 1024, numCtx 8192, thinking kwarg omitted, flush off).
|
|
98
|
+
* Wrong-typed values warn and are dropped (readPositiveConfig /
|
|
99
|
+
* readStrictBoolean / readNonNegativeConfig) — notably a JSON slip
|
|
100
|
+
* `"enableThinking": "false"` (string) is rejected rather than coerced to
|
|
101
|
+
* truthy thinking-on, which would silently invert the fix this key exists to
|
|
102
|
+
* apply.
|
|
102
103
|
*/
|
|
103
104
|
function applyTierTuningOverlay(llmConfig, savedConfig) {
|
|
104
105
|
if (!savedConfig)
|
|
@@ -106,6 +107,9 @@ function applyTierTuningOverlay(llmConfig, savedConfig) {
|
|
|
106
107
|
if (savedConfig.maxTokens !== undefined) {
|
|
107
108
|
llmConfig.maxTokens = (0, config_read_js_1.readPositiveConfig)(savedConfig, "maxTokens", 8192);
|
|
108
109
|
}
|
|
110
|
+
if (savedConfig.classifyMaxTokens !== undefined) {
|
|
111
|
+
llmConfig.classifyMaxTokens = (0, config_read_js_1.readPositiveConfig)(savedConfig, "classifyMaxTokens", 1024);
|
|
112
|
+
}
|
|
109
113
|
const thinking = (0, config_read_js_1.readStrictBoolean)(savedConfig, "enableThinking");
|
|
110
114
|
if (thinking !== undefined) {
|
|
111
115
|
llmConfig.enableThinking = thinking;
|
|
@@ -437,11 +441,18 @@ class LlmClient {
|
|
|
437
441
|
return this.complete(this.config.model, prompt, tokens, this.config.timeoutMs ?? 900_000);
|
|
438
442
|
}
|
|
439
443
|
/**
|
|
440
|
-
* Classification-tier completion (
|
|
441
|
-
* serves all phases (#231) — thin wrapper kept for call-site
|
|
444
|
+
* Classification-tier completion (verdicts + tag/type classification). One
|
|
445
|
+
* model serves all phases (#231) — thin wrapper kept for call-site
|
|
446
|
+
* readability, but the tier keeps its OWN output ceiling (#391).
|
|
442
447
|
*/
|
|
443
448
|
async completeClassify(prompt, maxTokens) {
|
|
444
|
-
|
|
449
|
+
// #391: classify-tier ceiling — the call sites' old hardcoded caps
|
|
450
|
+
// (64/32/20, tuned for a local non-reasoning model) starved reasoning
|
|
451
|
+
// models whose internal thinking consumed the whole budget, leaving
|
|
452
|
+
// verdicts empty. Deliberately NOT this.config.maxTokens: that knob
|
|
453
|
+
// governs the heavy phases; this tier has its own (a ceiling, not a
|
|
454
|
+
// target — generation still stops at the model's natural end).
|
|
455
|
+
const tokens = maxTokens ?? this.config.classifyMaxTokens ?? 1024;
|
|
445
456
|
return this.complete(this.config.model, prompt, tokens, this.config.timeoutMs ?? 900_000);
|
|
446
457
|
}
|
|
447
458
|
/**
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* POST /messages — message endpoint for MCP clients
|
|
12
12
|
*/
|
|
13
13
|
import express from "express";
|
|
14
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
15
|
import type { MemorySearchResult } from "./types.js";
|
|
15
16
|
/**
|
|
16
17
|
* Resolve the /distill probe gate (#337): true when the endpoint recently
|
|
@@ -28,6 +29,11 @@ export declare function resolveDistillProbeGate(llm: {
|
|
|
28
29
|
baseUrl: string;
|
|
29
30
|
probeTtlMs?: number;
|
|
30
31
|
}): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Build the McpServer with all Hicortex tools, one per /sse connection.
|
|
34
|
+
* Exported for tests (protocol-level initialize-result checks, #383).
|
|
35
|
+
*/
|
|
36
|
+
export declare function createMcpServer(): McpServer;
|
|
31
37
|
/**
|
|
32
38
|
* Resolve the request body-size limit in MB (#7, #328 item 2b). Pure —
|
|
33
39
|
* exported for tests. Precedence: HICORTEX_DISTILL_BODY_LIMIT_MB env >
|
package/dist/mcp-server.js
CHANGED
|
@@ -49,6 +49,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
49
49
|
};
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
51
|
exports.resolveDistillProbeGate = resolveDistillProbeGate;
|
|
52
|
+
exports.createMcpServer = createMcpServer;
|
|
52
53
|
exports.resolveBodyLimitMb = resolveBodyLimitMb;
|
|
53
54
|
exports.makeBodyLimitErrorHandler = makeBodyLimitErrorHandler;
|
|
54
55
|
exports.makeContentLengthGate = makeContentLengthGate;
|
|
@@ -84,6 +85,7 @@ const seed_lesson_js_1 = require("./seed-lesson.js");
|
|
|
84
85
|
const learnings_identity_js_1 = require("./learnings-identity.js");
|
|
85
86
|
const distiller_js_1 = require("./distiller.js");
|
|
86
87
|
const dedup_js_1 = require("./dedup.js");
|
|
88
|
+
const reconsolidation_js_1 = require("./reconsolidation.js");
|
|
87
89
|
const redact_js_1 = require("./redact.js");
|
|
88
90
|
const init_js_1 = require("./init.js");
|
|
89
91
|
// ---------------------------------------------------------------------------
|
|
@@ -163,11 +165,18 @@ catch { /* fallback */ }
|
|
|
163
165
|
// ---------------------------------------------------------------------------
|
|
164
166
|
// MCP Server setup
|
|
165
167
|
// ---------------------------------------------------------------------------
|
|
168
|
+
/**
|
|
169
|
+
* Build the McpServer with all Hicortex tools, one per /sse connection.
|
|
170
|
+
* Exported for tests (protocol-level initialize-result checks, #383).
|
|
171
|
+
*/
|
|
166
172
|
function createMcpServer() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
173
|
+
// #383: the initialize-result instructions are the only product-owned
|
|
174
|
+
// guidance passive MCP clients (e.g. Claude Desktop — no hooks, no injected
|
|
175
|
+
// sections) ever see: the MCP-native SessionStart. Same off-switch as the
|
|
176
|
+
// identity `memory` section (config `memoryInstructions: false`); the
|
|
177
|
+
// module var is read at call time, so per-connection construction
|
|
178
|
+
// preserves the boot-time gate.
|
|
179
|
+
const server = new mcp_js_1.McpServer({ name: "hicortex", version: VERSION }, { instructions: (0, memory_instructions_js_1.resolveMcpInstructions)(memoryInstructionsEnabled) });
|
|
171
180
|
// -- hicortex_search --
|
|
172
181
|
server.tool("hicortex_search", "Search shared long-term memory (all agents, all sessions). CALL THIS BEFORE assuming, guessing, or asking the user about anything that may have come up before: prior decisions, preferences, project facts, people, hardware, past incidents. If you are about to write 'I don't have information about…', search first.", {
|
|
173
182
|
query: zod_1.z.string().describe("Search query text"),
|
|
@@ -218,14 +227,30 @@ function createMcpServer() {
|
|
|
218
227
|
}
|
|
219
228
|
});
|
|
220
229
|
// -- hicortex_ingest --
|
|
221
|
-
server.tool("hicortex_ingest", "Store a new memory in long-term storage. Use for Knowledge, Decisions, or Learnings.", {
|
|
230
|
+
server.tool("hicortex_ingest", "Store a new memory in long-term storage. Use for Knowledge, Decisions, or Learnings. Capture is automatic (nightly) — use this ONLY for explicitly requested learnings, never routine content.", {
|
|
222
231
|
content: zod_1.z.string().describe("Memory content to store"),
|
|
223
232
|
project: zod_1.z.string().optional().describe("Project this memory belongs to"),
|
|
224
233
|
memory_type: zod_1.z.enum(["knowledge", "experience", "decisions", "learnings", "fact", "episode", "decision", "lesson"]).optional().describe("Type of memory (default: Experience). Accepted: Knowledge/Experience/Decisions/Learnings (legacy raw enum also accepted, normalized to the canonical term)."),
|
|
225
|
-
|
|
234
|
+
corrects: zod_1.z.string().optional().describe("ID (8-char prefix or full UUID) of an existing memory this one CORRECTS — records a corrected_by link and retracts the old memory (deterministic, no LLM). Mutually exclusive with supersedes."),
|
|
235
|
+
supersedes: zod_1.z.string().optional().describe("ID (8-char prefix or full UUID) of an existing memory this one SUPERSEDES — records a superseded_by link and marks the old memory superseded (deterministic, no LLM). Mutually exclusive with corrects."),
|
|
236
|
+
}, async ({ content, project, memory_type, corrects, supersedes }) => {
|
|
226
237
|
if (!db)
|
|
227
238
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
228
239
|
try {
|
|
240
|
+
// #384 explicit mark — validated BEFORE storing (an unknown/ambiguous
|
|
241
|
+
// target fails the whole call; nothing is written).
|
|
242
|
+
if (corrects !== undefined && supersedes !== undefined) {
|
|
243
|
+
return { content: [{ type: "text", text: "Provide at most one of 'corrects' or 'supersedes'" }], isError: true };
|
|
244
|
+
}
|
|
245
|
+
const rawMark = corrects !== undefined ? corrects : supersedes;
|
|
246
|
+
let explicitMark;
|
|
247
|
+
if (rawMark !== undefined) {
|
|
248
|
+
explicitMark = { kind: corrects !== undefined ? "corrects" : "supersedes", target: rawMark };
|
|
249
|
+
const check = (0, reconsolidation_js_1.checkExplicitMarkTarget)(db, explicitMark);
|
|
250
|
+
if (!check.ok) {
|
|
251
|
+
return { content: [{ type: "text", text: `Ingest failed: ${check.error}` }], isError: true };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
229
254
|
const embedding = await (0, embedder_js_1.embed)(content);
|
|
230
255
|
const id = storage.insertMemory(db, content, embedding, {
|
|
231
256
|
sourceAgent: "claude-code/manual",
|
|
@@ -233,7 +258,9 @@ function createMcpServer() {
|
|
|
233
258
|
// Normalize legacy raw enum to the canonical term the DB stores.
|
|
234
259
|
memoryType: memory_type ? (0, type_labels_js_1.normalizeMemoryType)(memory_type) : "experience",
|
|
235
260
|
});
|
|
236
|
-
|
|
261
|
+
if (explicitMark)
|
|
262
|
+
(0, reconsolidation_js_1.applyExplicitMark)(db, id, explicitMark);
|
|
263
|
+
return { content: [{ type: "text", text: `Memory stored (id: ${id.slice(0, 8)})${explicitMark ? ` — marked as ${explicitMark.kind === "corrects" ? "correcting" : "superseding"} ${explicitMark.target}` : ""}` }] };
|
|
237
264
|
}
|
|
238
265
|
catch (err) {
|
|
239
266
|
return { content: [{ type: "text", text: `Ingest failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
@@ -253,6 +280,12 @@ function createMcpServer() {
|
|
|
253
280
|
const fullId = resolveMemoryId(db, id);
|
|
254
281
|
if (!fullId)
|
|
255
282
|
return { content: [{ type: "text", text: `Memory not found: ${id}` }], isError: true };
|
|
283
|
+
// #384: absorbed memories are invisible evidence — an update would
|
|
284
|
+
// re-embed and resurrect them. Refuse; roll back the rewrite first.
|
|
285
|
+
const target = storage.getMemory(db, fullId);
|
|
286
|
+
if (target?.status === "absorbed") {
|
|
287
|
+
return { content: [{ type: "text", text: `Memory ${fullId.slice(0, 8)} is absorbed (folded into a corrected memory) — roll back the absorbing rewrite first (hicortex history --rollback)` }], isError: true };
|
|
288
|
+
}
|
|
256
289
|
const fields = {};
|
|
257
290
|
if (content !== undefined)
|
|
258
291
|
fields.content = content;
|
|
@@ -318,8 +351,8 @@ function createMcpServer() {
|
|
|
318
351
|
days: zod_1.z.coerce.number().optional().describe("Look back N days (default 7)"),
|
|
319
352
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
320
353
|
};
|
|
321
|
-
server.tool("hicortex_learnings", "Get actionable Learnings
|
|
322
|
-
server.tool("hicortex_lessons", "Get actionable Learnings from past sessions. (Alias for hicortex_learnings.)", learningsSchema, learningsHandler);
|
|
354
|
+
server.tool("hicortex_learnings", "Get actionable Learnings — auto-generated insights about mistakes to avoid. CALL THIS before retrying an approach that failed before, or when picking up work where past problems may have been recorded.", learningsSchema, learningsHandler);
|
|
355
|
+
server.tool("hicortex_lessons", "Get actionable Learnings from past sessions — call it before retrying an approach that failed before. (Alias for hicortex_learnings.)", learningsSchema, learningsHandler);
|
|
323
356
|
// -- hicortex_identity --
|
|
324
357
|
// Standing identity layer on-demand (the same data GET /identity returns and
|
|
325
358
|
// the SessionStart hook injects). Lets an agent re-read its identity after
|
|
@@ -351,7 +384,7 @@ function createMcpServer() {
|
|
|
351
384
|
}
|
|
352
385
|
});
|
|
353
386
|
// -- hicortex_index --
|
|
354
|
-
server.tool("hicortex_index", "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain.", {}, async () => {
|
|
387
|
+
server.tool("hicortex_index", "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain. Call before a broad search to see which knowledge domains exist, or when unsure what the memory covers.", {}, async () => {
|
|
355
388
|
const state = (0, state_js_1.loadState)(stateDir);
|
|
356
389
|
const moduleIndex = state.moduleIndex;
|
|
357
390
|
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
@@ -378,7 +411,7 @@ function createMcpServer() {
|
|
|
378
411
|
return { content: [{ type: "text", text }] };
|
|
379
412
|
});
|
|
380
413
|
// -- hicortex_graph --
|
|
381
|
-
server.tool("hicortex_graph", "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories.", {
|
|
414
|
+
server.tool("hicortex_graph", "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories. Use it to explore memories connected to one you just fetched, or to find hub memories in a domain.", {
|
|
382
415
|
operation: zod_1.z.enum(["neighbors", "hubs", "path"]).describe("Graph operation to perform"),
|
|
383
416
|
id: zod_1.z.string().optional().describe("Memory ID (required for neighbors and path operations)"),
|
|
384
417
|
target_id: zod_1.z.string().optional().describe("Target memory ID (required for path operation)"),
|
|
@@ -885,7 +918,7 @@ async function startServer(options = {}) {
|
|
|
885
918
|
res.status(503).json({ error: "Server not initialized" });
|
|
886
919
|
return;
|
|
887
920
|
}
|
|
888
|
-
const { content, source_agent, source_agent_id, source_domain, project, memory_type, privacy, source_session, session_date } = req.body ?? {};
|
|
921
|
+
const { content, source_agent, source_agent_id, source_domain, project, memory_type, privacy, source_session, session_date, corrects, supersedes } = req.body ?? {};
|
|
889
922
|
if (!content || typeof content !== "string") {
|
|
890
923
|
res.status(400).json({ error: "Missing or invalid 'content' field" });
|
|
891
924
|
return;
|
|
@@ -899,6 +932,28 @@ async function startServer(options = {}) {
|
|
|
899
932
|
// canonical term the DB stores (knowledge/experience/decisions/learnings).
|
|
900
933
|
// Canonical values pass through unchanged.
|
|
901
934
|
const normalizedType = memory_type ? (0, type_labels_js_1.normalizeMemoryType)(memory_type) : memory_type;
|
|
935
|
+
// #384 explicit write-time marking: `corrects` XOR `supersedes`, a single
|
|
936
|
+
// memory id reference. Validated BEFORE anything is written — an unknown
|
|
937
|
+
// or ambiguous target fails the WHOLE request (nothing is stored with a
|
|
938
|
+
// half-applied mark). Deterministic link + status, zero LLM.
|
|
939
|
+
if (corrects !== undefined && supersedes !== undefined) {
|
|
940
|
+
res.status(400).json({ error: "Provide at most one of 'corrects' or 'supersedes'" });
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
let explicitMark;
|
|
944
|
+
const rawMarkValue = corrects !== undefined ? corrects : supersedes;
|
|
945
|
+
if (rawMarkValue !== undefined) {
|
|
946
|
+
if (typeof rawMarkValue !== "string" || !rawMarkValue) {
|
|
947
|
+
res.status(400).json({ error: `'${corrects !== undefined ? "corrects" : "supersedes"}' must be a memory id (8-char prefix or full UUID)` });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
explicitMark = { kind: corrects !== undefined ? "corrects" : "supersedes", target: rawMarkValue };
|
|
951
|
+
const check = (0, reconsolidation_js_1.checkExplicitMarkTarget)(db, explicitMark);
|
|
952
|
+
if (!check.ok) {
|
|
953
|
+
res.status(check.httpStatus).json({ error: check.error });
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
902
957
|
// Dedup by source_session (idempotent — skip if already ingested)
|
|
903
958
|
if (source_session) {
|
|
904
959
|
const existing = db.prepare("SELECT COUNT(*) as cnt FROM memories WHERE source_session = ?").get(source_session);
|
|
@@ -922,7 +977,13 @@ async function startServer(options = {}) {
|
|
|
922
977
|
privacy: typeof privacy === "string" ? privacy : null,
|
|
923
978
|
createdAt: session_date ? new Date(session_date).toISOString() : undefined,
|
|
924
979
|
});
|
|
925
|
-
|
|
980
|
+
if (explicitMark)
|
|
981
|
+
(0, reconsolidation_js_1.applyExplicitMark)(db, id, explicitMark);
|
|
982
|
+
res.status(201).json({
|
|
983
|
+
id,
|
|
984
|
+
message: "Memory ingested",
|
|
985
|
+
...(explicitMark ? { marked: explicitMark.kind } : {}),
|
|
986
|
+
});
|
|
926
987
|
}
|
|
927
988
|
catch (err) {
|
|
928
989
|
res.status(500).json({ error: "Ingestion failed" });
|
|
@@ -1337,6 +1398,16 @@ async function startServer(options = {}) {
|
|
|
1337
1398
|
res.status(404).json({ error: `Memory not found: ${id}` });
|
|
1338
1399
|
return;
|
|
1339
1400
|
}
|
|
1401
|
+
// #384: an absorbed memory is invisible evidence — updating its content
|
|
1402
|
+
// would re-embed and resurrect a row the store deliberately folded into a
|
|
1403
|
+
// corrected target. Roll back the absorbing rewrite first.
|
|
1404
|
+
const target = storage.getMemory(db, fullId);
|
|
1405
|
+
if (target?.status === "absorbed") {
|
|
1406
|
+
res.status(409).json({
|
|
1407
|
+
error: `Memory ${fullId.slice(0, 8)} is absorbed (folded into a corrected memory) — roll back the absorbing rewrite first (hicortex history --rollback)`,
|
|
1408
|
+
});
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1340
1411
|
const fields = {};
|
|
1341
1412
|
if (content !== undefined)
|
|
1342
1413
|
fields.content = content;
|
|
@@ -1599,6 +1670,16 @@ async function startServer(options = {}) {
|
|
|
1599
1670
|
// (standard auth middleware, no shell exemption — it carries data); localhost
|
|
1600
1671
|
// bypass applies. Handler lives in src/dashboard.ts next to its twin.
|
|
1601
1672
|
app.get("/account", (0, dashboard_js_1.accountHandler)(() => readConfigFile(stateDir)));
|
|
1673
|
+
// GET /account/token — the install's connection token for the console
|
|
1674
|
+
// account menu (#365). Echo-only (see accountTokenHandler's JSDoc): the
|
|
1675
|
+
// caller must already present the token (bearer or localhost bypass), so
|
|
1676
|
+
// this grants no privilege — it exists so the menu shows authoritative
|
|
1677
|
+
// server-side truth instead of a possibly-stale localStorage copy. Passes
|
|
1678
|
+
// the boot-resolved PRIMARY token (the one the auth middleware itself
|
|
1679
|
+
// accepts as current — survives rotation, never echoes the grace token).
|
|
1680
|
+
// Bearer-only like /account: standard auth middleware, no shell exemption
|
|
1681
|
+
// (it carries data); localhost bypass applies.
|
|
1682
|
+
app.get("/account/token", (0, dashboard_js_1.accountTokenHandler)(() => authToken));
|
|
1602
1683
|
// SSE endpoint — each connection gets its own McpServer + transport
|
|
1603
1684
|
app.get("/sse", async (req, res) => {
|
|
1604
1685
|
const transport = new sse_js_1.SSEServerTransport("/messages", res);
|