memorix 1.6.0 → 1.6.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +317 -62
  4. package/dist/cli/memcode.js +12 -1
  5. package/dist/index.js +56 -9
  6. package/dist/maintenance-runner.js +54 -7
  7. package/dist/media-audio-runner.js +54 -7
  8. package/dist/media-video-runner.js +54 -7
  9. package/dist/memcode-runtime/CHANGELOG.md +24 -0
  10. package/dist/sdk.js +55 -8
  11. package/dist/vector-backfill-runner.js +54 -7
  12. package/docs/AGENT_OPERATOR_PLAYBOOK.md +11 -3
  13. package/docs/API_REFERENCE.md +1 -1
  14. package/docs/DESIGN_DECISIONS.md +357 -357
  15. package/docs/DOCKER.md +5 -1
  16. package/docs/PERFORMANCE.md +2 -2
  17. package/docs/README.md +3 -1
  18. package/docs/SETUP.md +8 -2
  19. package/package.json +1 -1
  20. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  21. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  22. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  23. package/plugins/copilot/memorix/plugin.json +1 -1
  24. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  25. package/plugins/omp/memorix/package.json +1 -1
  26. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/pi/memorix/package.json +1 -1
  28. package/server.json +2 -2
  29. package/src/audit/index.ts +156 -156
  30. package/src/cli/commands/agent-integrations.ts +9 -3
  31. package/src/cli/commands/doctor.ts +46 -15
  32. package/src/cli/commands/formation.ts +48 -48
  33. package/src/cli/commands/git-hook-install.ts +111 -111
  34. package/src/cli/commands/handoff.ts +54 -54
  35. package/src/cli/commands/hook.ts +13 -1
  36. package/src/cli/commands/hooks-status.ts +63 -63
  37. package/src/cli/commands/ingest.ts +44 -44
  38. package/src/cli/commands/integrate-shared.ts +15 -15
  39. package/src/cli/commands/lock.ts +82 -82
  40. package/src/cli/commands/message.ts +104 -104
  41. package/src/cli/commands/poll.ts +58 -58
  42. package/src/cli/commands/reasoning.ts +118 -118
  43. package/src/cli/commands/serve-http.ts +68 -7
  44. package/src/cli/commands/skills.ts +114 -114
  45. package/src/cli/commands/task.ts +167 -167
  46. package/src/cli/commands/transfer.ts +47 -47
  47. package/src/cli/heap.ts +76 -0
  48. package/src/cli/index.ts +406 -406
  49. package/src/cli/invocation.ts +7 -4
  50. package/src/cli/tui/ChatView.tsx +234 -234
  51. package/src/cli/tui/CommandBar.tsx +312 -312
  52. package/src/cli/tui/ContextRail.tsx +118 -118
  53. package/src/cli/tui/HeaderBar.tsx +72 -72
  54. package/src/cli/tui/LogoBanner.tsx +51 -51
  55. package/src/cli/tui/index.ts +41 -41
  56. package/src/cli/version.ts +7 -7
  57. package/src/cli/workbench.ts +1 -1
  58. package/src/dashboard/project-classification.ts +64 -64
  59. package/src/embedding/provider.ts +76 -6
  60. package/src/git/hooks-path.ts +85 -85
  61. package/src/hooks/handler.ts +9 -18
  62. package/src/hooks/lifecycle.ts +173 -0
  63. package/src/hooks/pattern-detector.ts +173 -173
  64. package/src/hooks/significance-filter.ts +250 -250
  65. package/src/llm/memory-manager.ts +328 -328
  66. package/src/llm/quality.ts +248 -248
  67. package/src/memory/attribution-guard.ts +249 -249
  68. package/src/memory/disclosure-policy.ts +135 -135
  69. package/src/memory/entity-extractor.ts +197 -197
  70. package/src/memory/formation/evaluate.ts +217 -217
  71. package/src/memory/freshness.ts +122 -122
  72. package/src/memory/graph.ts +197 -197
  73. package/src/memory/refs.ts +94 -94
  74. package/src/memory/secret-filter.ts +79 -79
  75. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  76. package/src/orchestrate/adapters/claude.ts +111 -111
  77. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  78. package/src/orchestrate/adapters/codex.ts +41 -41
  79. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  80. package/src/orchestrate/adapters/gemini.ts +42 -42
  81. package/src/orchestrate/adapters/index.ts +73 -73
  82. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  83. package/src/orchestrate/adapters/opencode.ts +47 -47
  84. package/src/orchestrate/adapters/types.ts +77 -77
  85. package/src/orchestrate/capability-router.ts +284 -284
  86. package/src/orchestrate/cost-tracker.ts +219 -219
  87. package/src/orchestrate/error-recovery.ts +191 -191
  88. package/src/orchestrate/evidence.ts +140 -140
  89. package/src/orchestrate/ledger.ts +110 -110
  90. package/src/orchestrate/memorix-bridge.ts +343 -343
  91. package/src/orchestrate/output-budget.ts +80 -80
  92. package/src/orchestrate/permission.ts +152 -152
  93. package/src/orchestrate/pipeline-trace.ts +131 -131
  94. package/src/orchestrate/prompt-builder.ts +155 -155
  95. package/src/orchestrate/ring-buffer.ts +37 -37
  96. package/src/orchestrate/task-graph.ts +389 -389
  97. package/src/project/aliases.ts +374 -374
  98. package/src/rules/adapters/trae.ts +56 -56
  99. package/src/rules/syncer.ts +235 -235
  100. package/src/search/query-expansion.ts +52 -52
  101. package/src/server/formation-timeout.ts +27 -27
  102. package/src/skills/mini-skills.ts +386 -386
  103. package/src/store/chat-store.ts +119 -119
  104. package/src/store/graph-store.ts +249 -249
  105. package/src/store/mini-skill-store.ts +349 -349
  106. package/src/store/persistence-json.ts +212 -212
  107. package/src/store/persistence.ts +291 -291
  108. package/src/store/project-affinity.ts +195 -195
  109. package/src/team/event-bus.ts +76 -76
  110. package/src/team/handoff.ts +161 -161
  111. package/src/team/poll.ts +132 -132
  112. package/src/workspace/mcp-adapters/codex.ts +191 -191
  113. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  114. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  115. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  116. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  117. package/src/workspace/mcp-adapters/trae.ts +134 -134
  118. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  119. package/src/workspace/sanitizer.ts +60 -60
package/dist/cli/index.js CHANGED
@@ -2,7 +2,18 @@
2
2
  import {spawnSync as __ms} from "node:child_process";
3
3
  import {fileURLToPath as __fu} from "node:url";
4
4
  if(!process.env.__MEMORIX_HEAP){process.env.__MEMORIX_HEAP="1";
5
- let r=__ms(process.execPath,["--max-old-space-size=4096",__fu(import.meta.url),...process.argv.slice(2)],{stdio:"inherit",env:process.env,windowsHide:true});
5
+ const __args=process.argv.slice(2);
6
+ let __cmd;
7
+ for(let __i=0;__i<__args.length;__i++){
8
+ const __a=__args[__i];
9
+ if(__a==="--")break;
10
+ if(__a.startsWith("-")){if(!__a.includes("=")&&__args[__i+1]&&!__args[__i+1].startsWith("-"))__i++;continue;}
11
+ __cmd=__a;break;}
12
+ const __fallback=__cmd==="hook"?512:4096;
13
+ const __raw=__cmd==="hook"?process.env.MEMORIX_HOOK_HEAP_MB:process.env.MEMORIX_HEAP_MB;
14
+ const __n=Number(__raw);
15
+ const __heap=(Number.isFinite(__n)&&__n>=64&&__n<=16384)?String(Math.floor(__n)):String(__fallback);
16
+ let r=__ms(process.execPath,["--max-old-space-size="+__heap,__fu(import.meta.url),...process.argv.slice(2)],{stdio:"inherit",env:process.env,windowsHide:true});
6
17
  process.exit(r.status??1);}
7
18
  import {createRequire as __memorix_cjsRequire} from "module";
8
19
  const require = __memorix_cjsRequire(import.meta.url);
@@ -3221,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3221
3232
 
3222
3233
  // src/cli/version.ts
3223
3234
  function getCliVersion() {
3224
- return true ? "1.6.0" : pkg.version;
3235
+ return true ? "1.6.1" : pkg.version;
3225
3236
  }
3226
3237
  var init_version = __esm({
3227
3238
  "src/cli/version.ts"() {
@@ -3231,7 +3242,7 @@ var init_version = __esm({
3231
3242
  });
3232
3243
 
3233
3244
  // src/cli/invocation.ts
3234
- import { existsSync as existsSync2, statSync } from "fs";
3245
+ import { existsSync as existsSync2, realpathSync, statSync } from "fs";
3235
3246
  import { resolve as resolve2 } from "path";
3236
3247
  function readOption(argument) {
3237
3248
  const equals = argument.indexOf("=");
@@ -3261,10 +3272,11 @@ function normalizeCliInvocation(argv2 = process.argv) {
3261
3272
  if (inlineValue === void 0) index += 1;
3262
3273
  }
3263
3274
  if (invocation.projectRoot) {
3264
- const projectRoot = resolve2(invocation.projectRoot);
3265
- if (!existsSync2(projectRoot) || !statSync(projectRoot).isDirectory()) {
3266
- throw new Error(`CLI project directory does not exist: ${projectRoot}`);
3275
+ const resolvedProjectRoot = resolve2(invocation.projectRoot);
3276
+ if (!existsSync2(resolvedProjectRoot) || !statSync(resolvedProjectRoot).isDirectory()) {
3277
+ throw new Error(`CLI project directory does not exist: ${resolvedProjectRoot}`);
3267
3278
  }
3279
+ const projectRoot = realpathSync(resolvedProjectRoot);
3268
3280
  process.chdir(projectRoot);
3269
3281
  process.env.MEMORIX_CLI_PROJECT_ROOT = projectRoot;
3270
3282
  invocation.projectRoot = projectRoot;
@@ -22778,6 +22790,7 @@ __export(provider_exports, {
22778
22790
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
22779
22791
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
22780
22792
  getEmbeddingProvider: () => getEmbeddingProvider,
22793
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
22781
22794
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
22782
22795
  isVectorSearchAvailable: () => isVectorSearchAvailable,
22783
22796
  resetProvider: () => resetProvider,
@@ -22885,13 +22898,36 @@ function isTemporaryEmbeddingFailure(error40) {
22885
22898
  if (!(error40 instanceof Error)) return false;
22886
22899
  return /embedding api error \(401\)/i.test(error40.message) || /invalid_api_key/i.test(error40.message) || /incorrect api key/i.test(error40.message) || /unauthorized/i.test(error40.message) || /embedding api timeout/i.test(error40.message) || /embedding api error \((402|429|5\d\d)\)/i.test(error40.message) || /quota exceeded/i.test(error40.message) || /account balance/i.test(error40.message) || /fetch failed/i.test(error40.message) || /econnreset/i.test(error40.message) || /econnrefused/i.test(error40.message) || /temporarily unavailable/i.test(error40.message);
22887
22900
  }
22901
+ function classifyEmbeddingFailure(error40) {
22902
+ const message = error40 instanceof Error ? error40.message : String(error40);
22903
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
22904
+ return "authorization";
22905
+ }
22906
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
22907
+ return "billing";
22908
+ }
22909
+ if (/embedding api timeout|abort/i.test(message)) {
22910
+ return "timeout";
22911
+ }
22912
+ return "upstream";
22913
+ }
22888
22914
  function markTemporaryFailure(reason) {
22889
22915
  provider = null;
22890
22916
  initPromise = null;
22891
22917
  lastInitWasTemporaryFailure = true;
22892
22918
  lastFailureTimestamp = Date.now();
22919
+ lastEmbeddingFailure = {
22920
+ kind: classifyEmbeddingFailure(reason),
22921
+ occurredAt: lastFailureTimestamp
22922
+ };
22893
22923
  const message = reason instanceof Error ? reason.message : String(reason);
22894
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
22924
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
22925
+ }
22926
+ function markProviderReady(candidate2) {
22927
+ provider = wrapProvider(candidate2);
22928
+ lastInitWasTemporaryFailure = false;
22929
+ lastEmbeddingFailure = null;
22930
+ return provider;
22895
22931
  }
22896
22932
  async function createFastEmbedProvider() {
22897
22933
  try {
@@ -23007,35 +23043,35 @@ async function getEmbeddingProvider(options = {}) {
23007
23043
  if (mode === "fastembed") {
23008
23044
  const initialized = await createFastEmbedProvider();
23009
23045
  if (!initialized) return null;
23010
- provider = wrapProvider(initialized);
23046
+ provider = markProviderReady(initialized);
23011
23047
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
23012
23048
  return provider;
23013
23049
  }
23014
23050
  if (mode === "transformers") {
23015
23051
  const initialized = await createTransformersProvider();
23016
23052
  if (!initialized) return null;
23017
- provider = wrapProvider(initialized);
23053
+ provider = markProviderReady(initialized);
23018
23054
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
23019
23055
  return provider;
23020
23056
  }
23021
23057
  if (mode === "api") {
23022
23058
  const initialized = await createAPIProvider(options);
23023
23059
  if (!initialized) return null;
23024
- provider = wrapProvider(initialized);
23060
+ provider = markProviderReady(initialized);
23025
23061
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
23026
23062
  return provider;
23027
23063
  }
23028
23064
  if (hasAPIEmbeddingConfig()) {
23029
23065
  const initialized = await createAPIProvider(options);
23030
23066
  if (initialized) {
23031
- provider = wrapProvider(initialized);
23067
+ provider = markProviderReady(initialized);
23032
23068
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
23033
23069
  return provider;
23034
23070
  }
23035
23071
  }
23036
23072
  const localFallback = await createLocalFallbackProvider();
23037
23073
  if (localFallback) {
23038
- provider = wrapProvider(localFallback);
23074
+ provider = markProviderReady(localFallback);
23039
23075
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
23040
23076
  return provider;
23041
23077
  }
@@ -23057,6 +23093,27 @@ async function isVectorSearchAvailable() {
23057
23093
  const p3 = await getEmbeddingProvider();
23058
23094
  return p3 !== null;
23059
23095
  }
23096
+ function getEmbeddingRuntimeHealth() {
23097
+ const mode = getEmbeddingMode2();
23098
+ if (mode === "off") return { status: "disabled" };
23099
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
23100
+ return {
23101
+ status: "degraded",
23102
+ failure: {
23103
+ kind: lastEmbeddingFailure.kind,
23104
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
23105
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
23106
+ }
23107
+ };
23108
+ }
23109
+ if (provider) {
23110
+ return {
23111
+ status: "ready",
23112
+ provider: { name: provider.name, dimensions: provider.dimensions }
23113
+ };
23114
+ }
23115
+ return { status: "uninitialized" };
23116
+ }
23060
23117
  function isEmbeddingExplicitlyDisabled() {
23061
23118
  return getEmbeddingMode2() === "off";
23062
23119
  }
@@ -23065,10 +23122,11 @@ function resetProvider() {
23065
23122
  initPromise = null;
23066
23123
  lastInitWasTemporaryFailure = false;
23067
23124
  lastFailureTimestamp = 0;
23125
+ lastEmbeddingFailure = null;
23068
23126
  lastEmbeddingWarning = "";
23069
23127
  lastEmbeddingWarningAt = 0;
23070
23128
  }
23071
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
23129
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
23072
23130
  var init_provider = __esm({
23073
23131
  "src/embedding/provider.ts"() {
23074
23132
  "use strict";
@@ -23093,6 +23151,7 @@ var init_provider = __esm({
23093
23151
  provider = null;
23094
23152
  initPromise = null;
23095
23153
  lastInitWasTemporaryFailure = false;
23154
+ lastEmbeddingFailure = null;
23096
23155
  lastEmbeddingWarning = "";
23097
23156
  lastEmbeddingWarningAt = 0;
23098
23157
  WARNING_COOLDOWN_MS = 3e4;
@@ -259024,7 +259083,7 @@ The path should point to a directory containing a .git folder.`
259024
259083
  };
259025
259084
  const server = existingServer ?? new McpServer({
259026
259085
  name: "memorix",
259027
- version: true ? "1.6.0" : "1.0.1"
259086
+ version: true ? "1.6.1" : "1.0.1"
259028
259087
  });
259029
259088
  const originalRegisterTool = server.registerTool.bind(server);
259030
259089
  server.registerTool = ((name, ...args) => {
@@ -265892,6 +265951,7 @@ var init_control_plane_maintenance = __esm({
265892
265951
  var serve_http_exports = {};
265893
265952
  __export(serve_http_exports, {
265894
265953
  DEFAULT_SESSION_TIMEOUT_MS: () => DEFAULT_SESSION_TIMEOUT_MS,
265954
+ EXPIRED_SESSION_TTL_MS: () => EXPIRED_SESSION_TTL_MS,
265895
265955
  _testing: () => _testing2,
265896
265956
  default: () => serve_http_default,
265897
265957
  parseSessionTimeoutMs: () => parseSessionTimeoutMs,
@@ -265901,7 +265961,7 @@ function parseSessionTimeoutMs(raw) {
265901
265961
  const value = raw?.trim();
265902
265962
  if (!value) return DEFAULT_SESSION_TIMEOUT_MS;
265903
265963
  const parsed = Number(value);
265904
- if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_SESSION_TIMEOUT_MS;
265964
+ if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SESSION_TIMEOUT_MS;
265905
265965
  return Math.floor(parsed);
265906
265966
  }
265907
265967
  function resolveServeHttpDefaults(argsPort, configured) {
@@ -265913,7 +265973,7 @@ function resolveServeHttpDefaults(argsPort, configured) {
265913
265973
  const transportNotice = configured.transport === "stdio" ? '[memorix] config server.transport = "stdio"; serve-http continues on HTTP (the explicit command wins).' : void 0;
265914
265974
  return { port, transportNotice };
265915
265975
  }
265916
- var DEFAULT_SESSION_TIMEOUT_MS, serve_http_default, _testing2;
265976
+ var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, serve_http_default, _testing2;
265917
265977
  var init_serve_http = __esm({
265918
265978
  "src/cli/commands/serve-http.ts"() {
265919
265979
  "use strict";
@@ -265925,7 +265985,8 @@ var init_serve_http = __esm({
265925
265985
  init_visibility();
265926
265986
  init_port();
265927
265987
  init_mcp_root_path();
265928
- DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
265988
+ DEFAULT_SESSION_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
265989
+ EXPIRED_SESSION_TTL_MS = 10 * 60 * 1e3;
265929
265990
  serve_http_default = defineCommand({
265930
265991
  meta: {
265931
265992
  name: "serve-http",
@@ -265984,7 +266045,9 @@ var init_serve_http = __esm({
265984
266045
  console.error(`[memorix] HTTP transport starting on ${host}:${port}`);
265985
266046
  console.error(`[memorix] Project root: ${projectRoot}`);
265986
266047
  const sessionTimeoutMs = parseSessionTimeoutMs(process.env.MEMORIX_SESSION_TIMEOUT_MS);
265987
- console.error(`[memorix] HTTP session idle timeout: ${Math.round(sessionTimeoutMs / 6e4)}min (${sessionTimeoutMs}ms)`);
266048
+ console.error(
266049
+ sessionTimeoutMs === 0 ? "[memorix] HTTP session idle timeout: disabled" : `[memorix] HTTP session idle timeout: ${Math.round(sessionTimeoutMs / 6e4)}min (${sessionTimeoutMs}ms)`
266050
+ );
265988
266051
  const teamStoreCache = /* @__PURE__ */ new Map();
265989
266052
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
265990
266053
  async function getTeamStore2(dataDir) {
@@ -266001,6 +266064,7 @@ var init_serve_http = __esm({
266001
266064
  const sharedTeamStore = await getTeamStore2(teamDataDir);
266002
266065
  const sessions = /* @__PURE__ */ new Map();
266003
266066
  const sessionLastActivity = /* @__PURE__ */ new Map();
266067
+ const expiredSessions = /* @__PURE__ */ new Map();
266004
266068
  let suppressedProbeCount = 0;
266005
266069
  let lastProbeSummaryTime = Date.now();
266006
266070
  const PROBE_SUMMARY_INTERVAL_MS = 6e4;
@@ -266013,6 +266077,26 @@ var init_serve_http = __esm({
266013
266077
  lastProbeSummaryTime = Date.now();
266014
266078
  }
266015
266079
  }
266080
+ function forgetExpiredSessions(now3 = Date.now()) {
266081
+ for (const [sessionId, expiresAt] of expiredSessions) {
266082
+ if (expiresAt <= now3) expiredSessions.delete(sessionId);
266083
+ }
266084
+ }
266085
+ function sendUnknownSession(res, sessionId, jsonRpc = false) {
266086
+ const expired = Boolean(sessionId && expiredSessions.has(sessionId));
266087
+ const message = expired ? "MCP session expired after inactivity. Initialize a new session and retry the request." : "MCP session not found. Initialize a new session and retry the request.";
266088
+ if (jsonRpc) {
266089
+ res.writeHead(404, { "Content-Type": "application/json" });
266090
+ res.end(JSON.stringify({
266091
+ jsonrpc: "2.0",
266092
+ error: { code: -32001, message },
266093
+ id: null
266094
+ }));
266095
+ return;
266096
+ }
266097
+ res.writeHead(404, { "Content-Type": "text/plain" });
266098
+ res.end(message);
266099
+ }
266016
266100
  function parseBody(req) {
266017
266101
  return new Promise((resolve6, reject) => {
266018
266102
  const chunks = [];
@@ -266129,6 +266213,10 @@ var init_serve_http = __esm({
266129
266213
  await sessions.get(sessionId).transport.handleRequest(req, res, body);
266130
266214
  return;
266131
266215
  }
266216
+ if (sessionId) {
266217
+ sendUnknownSession(res, sessionId, true);
266218
+ return;
266219
+ }
266132
266220
  if (!sessionId && isInitializeRequest2(body)) {
266133
266221
  let createdState = null;
266134
266222
  const transport = new StreamableHTTPServerTransport2({
@@ -266227,8 +266315,12 @@ var init_serve_http = __esm({
266227
266315
  async function handleGet(req, res) {
266228
266316
  const sessionId = req.headers["mcp-session-id"];
266229
266317
  if (!sessionId || !sessions.has(sessionId)) {
266230
- res.writeHead(400, { "Content-Type": "text/plain" });
266231
- res.end("Invalid or missing session ID");
266318
+ if (!sessionId) {
266319
+ res.writeHead(400, { "Content-Type": "text/plain" });
266320
+ res.end("Missing MCP session ID");
266321
+ return;
266322
+ }
266323
+ sendUnknownSession(res, sessionId);
266232
266324
  return;
266233
266325
  }
266234
266326
  await sessions.get(sessionId).transport.handleRequest(req, res);
@@ -266236,8 +266328,12 @@ var init_serve_http = __esm({
266236
266328
  async function handleDelete(req, res) {
266237
266329
  const sessionId = req.headers["mcp-session-id"];
266238
266330
  if (!sessionId || !sessions.has(sessionId)) {
266239
- res.writeHead(400, { "Content-Type": "text/plain" });
266240
- res.end("Invalid or missing session ID");
266331
+ if (!sessionId) {
266332
+ res.writeHead(400, { "Content-Type": "text/plain" });
266333
+ res.end("Missing MCP session ID");
266334
+ return;
266335
+ }
266336
+ sendUnknownSession(res, sessionId);
266241
266337
  return;
266242
266338
  }
266243
266339
  await sessions.get(sessionId).transport.handleRequest(req, res);
@@ -266986,13 +267082,15 @@ var init_serve_http = __esm({
266986
267082
  }
266987
267083
  const url2 = new URL(req.url || "/", `http://localhost:${port}`);
266988
267084
  if (url2.pathname === "/health") {
267085
+ const { getEmbeddingRuntimeHealth: getEmbeddingRuntimeHealth2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
266989
267086
  res.writeHead(200, { "Content-Type": "application/json" });
266990
267087
  res.end(JSON.stringify({
266991
267088
  status: "ok",
266992
267089
  mode: "control-plane",
266993
267090
  port,
266994
267091
  uptime: Math.round(process.uptime()),
266995
- pid: process.pid
267092
+ pid: process.pid,
267093
+ embedding: getEmbeddingRuntimeHealth2()
266996
267094
  }));
266997
267095
  return;
266998
267096
  }
@@ -267050,6 +267148,8 @@ var init_serve_http = __esm({
267050
267148
  }
267051
267149
  await serveDashStatic(req, res);
267052
267150
  });
267151
+ httpServer.keepAliveTimeout = 6e4;
267152
+ httpServer.headersTimeout = 65e3;
267053
267153
  httpServer.listen(port, host, () => {
267054
267154
  try {
267055
267155
  const memorixDir = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/") + "/.memorix";
@@ -267073,10 +267173,13 @@ var init_serve_http = __esm({
267073
267173
  const gcInterval = setInterval(() => {
267074
267174
  maybeProbeSummary();
267075
267175
  const now3 = Date.now();
267176
+ forgetExpiredSessions(now3);
267177
+ if (SESSION_TIMEOUT_MS === 0) return;
267076
267178
  for (const [sid, state] of sessions) {
267077
267179
  const lastActive = sessionLastActivity.get(sid) ?? 0;
267078
267180
  if (now3 - lastActive > SESSION_TIMEOUT_MS) {
267079
267181
  console.error(`[memorix] Session ${sid.slice(0, 8)}\u2026 timed out (idle ${Math.round((now3 - lastActive) / 6e4)}min), closing`);
267182
+ expiredSessions.set(sid, now3 + EXPIRED_SESSION_TTL_MS);
267080
267183
  state.transport.close().catch(() => {
267081
267184
  });
267082
267185
  sessions.delete(sid);
@@ -268715,6 +268818,144 @@ var init_pattern_detector = __esm({
268715
268818
  }
268716
268819
  });
268717
268820
 
268821
+ // src/hooks/lifecycle.ts
268822
+ var lifecycle_exports2 = {};
268823
+ __export(lifecycle_exports2, {
268824
+ DEFAULT_HOOK_STDIN_TIMEOUT_MS: () => DEFAULT_HOOK_STDIN_TIMEOUT_MS,
268825
+ DEFAULT_HOOK_TIMEOUT_MS: () => DEFAULT_HOOK_TIMEOUT_MS,
268826
+ MAX_HOOK_STDIN_TIMEOUT_MS: () => MAX_HOOK_STDIN_TIMEOUT_MS,
268827
+ MAX_HOOK_TIMEOUT_MS: () => MAX_HOOK_TIMEOUT_MS,
268828
+ MIN_HOOK_STDIN_TIMEOUT_MS: () => MIN_HOOK_STDIN_TIMEOUT_MS,
268829
+ MIN_HOOK_TIMEOUT_MS: () => MIN_HOOK_TIMEOUT_MS,
268830
+ hookObservationRuntimeOptions: () => hookObservationRuntimeOptions,
268831
+ readHookStdin: () => readHookStdin,
268832
+ resolveHookStdinTimeoutMs: () => resolveHookStdinTimeoutMs,
268833
+ resolveHookTimeoutMs: () => resolveHookTimeoutMs,
268834
+ runHookWithDeadline: () => runHookWithDeadline
268835
+ });
268836
+ function parseBoundedMs(raw, fallback, min, max) {
268837
+ const value = raw?.trim();
268838
+ if (!value) return fallback;
268839
+ const parsed = Number(value);
268840
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
268841
+ return Math.max(min, Math.min(max, Math.floor(parsed)));
268842
+ }
268843
+ function resolveHookTimeoutMs(env3 = process.env) {
268844
+ return parseBoundedMs(
268845
+ env3.MEMORIX_HOOK_TIMEOUT_MS,
268846
+ DEFAULT_HOOK_TIMEOUT_MS,
268847
+ MIN_HOOK_TIMEOUT_MS,
268848
+ MAX_HOOK_TIMEOUT_MS
268849
+ );
268850
+ }
268851
+ function resolveHookStdinTimeoutMs(env3 = process.env) {
268852
+ return parseBoundedMs(
268853
+ env3.MEMORIX_HOOK_STDIN_TIMEOUT_MS,
268854
+ DEFAULT_HOOK_STDIN_TIMEOUT_MS,
268855
+ MIN_HOOK_STDIN_TIMEOUT_MS,
268856
+ MAX_HOOK_STDIN_TIMEOUT_MS
268857
+ );
268858
+ }
268859
+ function hookObservationRuntimeOptions(projectRoot) {
268860
+ return {
268861
+ embeddingWriteMode: "deferred",
268862
+ projectRoot
268863
+ };
268864
+ }
268865
+ async function readHookStdin(stdin2, timeoutMs) {
268866
+ return new Promise((resolve6) => {
268867
+ const chunks = [];
268868
+ let settled = false;
268869
+ const finish = () => {
268870
+ if (settled) return;
268871
+ settled = true;
268872
+ clearTimeout(timer);
268873
+ stdin2.removeListener("data", onData);
268874
+ stdin2.removeListener("end", finish);
268875
+ stdin2.removeListener("error", finish);
268876
+ stdin2.pause?.();
268877
+ if (!stdin2.isTTY) {
268878
+ stdin2.destroy?.();
268879
+ } else {
268880
+ stdin2.unref?.();
268881
+ }
268882
+ resolve6(Buffer.concat(chunks).toString("utf-8").trim());
268883
+ };
268884
+ const onData = (chunk) => {
268885
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
268886
+ };
268887
+ const timer = setTimeout(finish, timeoutMs);
268888
+ stdin2.on("data", onData);
268889
+ stdin2.on("end", finish);
268890
+ stdin2.on("error", finish);
268891
+ });
268892
+ }
268893
+ function writeContinue(stdout2) {
268894
+ try {
268895
+ stdout2.write(JSON.stringify({ continue: true }));
268896
+ } catch {
268897
+ }
268898
+ }
268899
+ async function runHookWithDeadline(options) {
268900
+ const stdout2 = options.stdout ?? process.stdout;
268901
+ const stderr = options.stderr ?? process.stderr;
268902
+ const exit = options.exit ?? ((code) => {
268903
+ process.exit(code);
268904
+ });
268905
+ await new Promise((resolve6) => {
268906
+ let finished2 = false;
268907
+ const settle = () => {
268908
+ if (finished2) return;
268909
+ finished2 = true;
268910
+ clearTimeout(timer);
268911
+ resolve6();
268912
+ };
268913
+ const timer = setTimeout(() => {
268914
+ if (finished2) return;
268915
+ try {
268916
+ stderr.write(`[memorix] hook timed out after ${options.timeoutMs}ms
268917
+ `);
268918
+ } catch {
268919
+ }
268920
+ writeContinue(stdout2);
268921
+ exit(0);
268922
+ settle();
268923
+ }, options.timeoutMs);
268924
+ options.run().then(
268925
+ () => {
268926
+ if (finished2) return;
268927
+ exit(0);
268928
+ settle();
268929
+ },
268930
+ (error40) => {
268931
+ if (finished2) return;
268932
+ try {
268933
+ const message = error40 instanceof Error ? error40.message : String(error40);
268934
+ stderr.write(`[memorix] hook failed: ${message}
268935
+ `);
268936
+ } catch {
268937
+ }
268938
+ writeContinue(stdout2);
268939
+ exit(0);
268940
+ settle();
268941
+ }
268942
+ );
268943
+ });
268944
+ }
268945
+ var DEFAULT_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS, MAX_HOOK_TIMEOUT_MS, DEFAULT_HOOK_STDIN_TIMEOUT_MS, MIN_HOOK_STDIN_TIMEOUT_MS, MAX_HOOK_STDIN_TIMEOUT_MS;
268946
+ var init_lifecycle2 = __esm({
268947
+ "src/hooks/lifecycle.ts"() {
268948
+ "use strict";
268949
+ init_esm_shims();
268950
+ DEFAULT_HOOK_TIMEOUT_MS = 2e4;
268951
+ MIN_HOOK_TIMEOUT_MS = 1e3;
268952
+ MAX_HOOK_TIMEOUT_MS = 12e4;
268953
+ DEFAULT_HOOK_STDIN_TIMEOUT_MS = 3e3;
268954
+ MIN_HOOK_STDIN_TIMEOUT_MS = 100;
268955
+ MAX_HOOK_STDIN_TIMEOUT_MS = 3e4;
268956
+ }
268957
+ });
268958
+
268718
268959
  // src/hooks/handler.ts
268719
268960
  var handler_exports = {};
268720
268961
  __export(handler_exports, {
@@ -269194,26 +269435,7 @@ function formatHookOutput(agent, rawEventName, output) {
269194
269435
  return finalOutput;
269195
269436
  }
269196
269437
  async function runHook(agentOverride, eventOverride) {
269197
- const rawInput = await new Promise((resolve6) => {
269198
- const chunks = [];
269199
- const finish = () => resolve6(Buffer.concat(chunks).toString("utf-8").trim());
269200
- const timer = setTimeout(() => {
269201
- process.stdin.removeAllListeners("data");
269202
- process.stdin.removeAllListeners("end");
269203
- finish();
269204
- }, 3e3);
269205
- process.stdin.on("data", (chunk) => {
269206
- chunks.push(chunk);
269207
- });
269208
- process.stdin.on("end", () => {
269209
- clearTimeout(timer);
269210
- finish();
269211
- });
269212
- process.stdin.on("error", () => {
269213
- clearTimeout(timer);
269214
- finish();
269215
- });
269216
- });
269438
+ const rawInput = await readHookStdin(process.stdin, resolveHookStdinTimeoutMs());
269217
269439
  if (!rawInput) {
269218
269440
  process.stdout.write(JSON.stringify({ continue: true }));
269219
269441
  return;
@@ -269263,7 +269485,7 @@ async function runHook(agentOverride, eventOverride) {
269263
269485
  await initObservationStore2(dataDir);
269264
269486
  await initMSStore(dataDir);
269265
269487
  await initSessStore(dataDir);
269266
- await initObservations2(dataDir);
269488
+ await initObservations2(dataDir, hookObservationRuntimeOptions(rawProject.rootPath));
269267
269489
  await storeObservation2({ ...observation, projectId, sourceDetail: "hook" });
269268
269490
  } catch (storeErr) {
269269
269491
  console.error("[memorix] hook store failed:", storeErr?.message ?? storeErr);
@@ -269309,6 +269531,7 @@ var init_handler = __esm({
269309
269531
  init_normalizer();
269310
269532
  init_pattern_detector();
269311
269533
  init_significance_filter();
269534
+ init_lifecycle2();
269312
269535
  TYPE_EMOJI2 = {
269313
269536
  "gotcha": "[GOTCHA]",
269314
269537
  "decision": "[DECISION]",
@@ -269365,7 +269588,11 @@ var init_hook = __esm({
269365
269588
  },
269366
269589
  run: async ({ args }) => {
269367
269590
  const { runHook: runHook2 } = await Promise.resolve().then(() => (init_handler(), handler_exports));
269368
- await runHook2(args.agent, args.event);
269591
+ const { resolveHookTimeoutMs: resolveHookTimeoutMs2, runHookWithDeadline: runHookWithDeadline2 } = await Promise.resolve().then(() => (init_lifecycle2(), lifecycle_exports2));
269592
+ await runHookWithDeadline2({
269593
+ timeoutMs: resolveHookTimeoutMs2(),
269594
+ run: () => runHook2(args.agent, args.event)
269595
+ });
269369
269596
  }
269370
269597
  });
269371
269598
  }
@@ -272131,7 +272358,7 @@ __export(agent_integrations_exports, {
272131
272358
  repairAgentIntegrations: () => repairAgentIntegrations
272132
272359
  });
272133
272360
  import { spawnSync as spawnSync2 } from "child_process";
272134
- import { existsSync as existsSync28 } from "fs";
272361
+ import { existsSync as existsSync28, realpathSync as realpathSync2 } from "fs";
272135
272362
  import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
272136
272363
  import { basename as basename5, dirname as dirname8, resolve as resolve4 } from "path";
272137
272364
  import { homedir as homedir34 } from "os";
@@ -272525,10 +272752,15 @@ function sanitizeServer(server) {
272525
272752
  };
272526
272753
  }
272527
272754
  function normalizeProjectKey(projectRoot) {
272528
- return resolve4(projectRoot).replace(/\\/g, "/").toLowerCase();
272755
+ return canonicalProjectPath(projectRoot).toLowerCase();
272529
272756
  }
272530
272757
  function defaultClaudeProjectKey(projectRoot) {
272531
- return resolve4(projectRoot).replace(/\\/g, "/");
272758
+ return canonicalProjectPath(projectRoot);
272759
+ }
272760
+ function canonicalProjectPath(projectRoot) {
272761
+ const resolved = resolve4(projectRoot);
272762
+ const canonical = existsSync28(resolved) ? realpathSync2(resolved) : resolved;
272763
+ return canonical.replace(/\\/g, "/");
272532
272764
  }
272533
272765
  function getClaudeLocalConfigPath() {
272534
272766
  return `${homedir34()}/.claude.json`;
@@ -273137,6 +273369,7 @@ var init_doctor = __esm({
273137
273369
  let bgRunning = false;
273138
273370
  let bgPort = 0;
273139
273371
  let bgPid = 0;
273372
+ let backgroundHealthy = false;
273140
273373
  try {
273141
273374
  if (existsSync33(bgStatePath)) {
273142
273375
  const bgState = JSON.parse(readFileSync22(bgStatePath, "utf-8"));
@@ -273154,7 +273387,7 @@ var init_doctor = __esm({
273154
273387
  lines.push(ok(`Background control plane running (PID ${bgPid}, port ${bgPort})`));
273155
273388
  try {
273156
273389
  const http3 = await import("http");
273157
- const healthy = await new Promise((resolve6) => {
273390
+ backgroundHealthy = await new Promise((resolve6) => {
273158
273391
  const req = http3.request({ hostname: "127.0.0.1", port: bgPort, path: "/api/team", timeout: 3e3 }, (res) => {
273159
273392
  res.resume();
273160
273393
  resolve6(res.statusCode === 200);
@@ -273166,7 +273399,7 @@ var init_doctor = __esm({
273166
273399
  });
273167
273400
  req.end();
273168
273401
  });
273169
- if (healthy) {
273402
+ if (backgroundHealthy) {
273170
273403
  lines.push(ok("Health check: OK"));
273171
273404
  } else {
273172
273405
  lines.push(warn2("Health check: FAILED (process alive but not responding)"));
@@ -273174,8 +273407,9 @@ var init_doctor = __esm({
273174
273407
  }
273175
273408
  } catch {
273176
273409
  lines.push(warn2("Health check: could not connect"));
273410
+ issues.push('Background process is alive but its health endpoint could not be reached. Try "memorix background restart".');
273177
273411
  }
273178
- report.mode = { type: "background", pid: bgPid, port: bgPort, healthy: true };
273412
+ report.mode = { type: "background", pid: bgPid, port: bgPort, healthy: backgroundHealthy };
273179
273413
  } else {
273180
273414
  lines.push(info2("Background control plane: not running"));
273181
273415
  lines.push(info2("Current invocation: CLI (stdio)"));
@@ -273216,18 +273450,39 @@ var init_doctor = __esm({
273216
273450
  report.embedding = { mode: "off", status: "disabled" };
273217
273451
  } else {
273218
273452
  lines.push(ok(`Mode: ${mode}`));
273219
- const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
273220
- const provider2 = await getEmbeddingProvider2();
273221
- if (provider2) {
273222
- lines.push(ok(`Provider: ${provider2.name} (${provider2.dimensions}d)`));
273223
- lines.push(ok("Status: ready"));
273224
- lines.push(info2("Search: hybrid (BM25 + vector)"));
273225
- report.embedding = { mode, status: "ready", provider: provider2.name, dimensions: provider2.dimensions };
273453
+ let controlPlaneEmbedding;
273454
+ if (bgRunning) {
273455
+ try {
273456
+ const response = await fetch(`http://127.0.0.1:${bgPort}/health`, { signal: AbortSignal.timeout(3e3) });
273457
+ if (response.ok) {
273458
+ const health = await response.json();
273459
+ controlPlaneEmbedding = health.embedding;
273460
+ }
273461
+ } catch {
273462
+ }
273463
+ }
273464
+ if (controlPlaneEmbedding?.status === "degraded") {
273465
+ const kind = controlPlaneEmbedding.failure?.kind ?? "upstream";
273466
+ const retryAfterMs = controlPlaneEmbedding.failure?.retryAfterMs;
273467
+ const retry2 = typeof retryAfterMs === "number" && retryAfterMs > 0 ? `; retry in ${Math.ceil(retryAfterMs / 1e3)}s` : "";
273468
+ lines.push(warn2(`Status: degraded in the running control plane (${kind}${retry2})`));
273469
+ lines.push(info2("Search: degraded to BM25 fulltext until the provider recovers"));
273470
+ issues.push("The running control plane has an unavailable embedding provider. Check provider credentials, billing, or connectivity.");
273471
+ report.embedding = { mode, status: "degraded", failure: kind };
273226
273472
  } else {
273227
- lines.push(warn2("Status: temporarily unavailable"));
273228
- lines.push(info2("Search: degraded to BM25 fulltext (no vector similarity)"));
273229
- issues.push(`Embedding mode is "${mode}" but provider failed to initialize. Check API keys/connectivity.`);
273230
- report.embedding = { mode, status: "temporarily_unavailable" };
273473
+ const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
273474
+ const provider2 = await getEmbeddingProvider2();
273475
+ if (provider2) {
273476
+ lines.push(ok(`Provider: ${provider2.name} (${provider2.dimensions}d)`));
273477
+ lines.push(ok("Status: ready"));
273478
+ lines.push(info2("Search: hybrid (BM25 + vector)"));
273479
+ report.embedding = { mode, status: "ready", provider: provider2.name, dimensions: provider2.dimensions };
273480
+ } else {
273481
+ lines.push(warn2("Status: temporarily unavailable"));
273482
+ lines.push(info2("Search: degraded to BM25 fulltext (no vector similarity)"));
273483
+ issues.push(`Embedding mode is "${mode}" but provider failed to initialize. Check API keys/connectivity.`);
273484
+ report.embedding = { mode, status: "temporarily_unavailable" };
273485
+ }
273231
273486
  }
273232
273487
  try {
273233
273488
  const cachePath = join34(memorixDir, "embedding-cache.json");