memorix 1.6.0 → 1.6.2
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/CHANGELOG.md +46 -0
- package/dist/cli/index.js +1888 -1122
- package/dist/cli/memcode.js +12 -1
- package/dist/index.js +641 -113
- package/dist/maintenance-runner.js +618 -90
- package/dist/media-audio-runner.js +618 -90
- package/dist/media-video-runner.js +618 -90
- package/dist/memcode-runtime/CHANGELOG.md +46 -0
- package/dist/sdk.js +674 -117
- package/dist/vector-backfill-runner.js +618 -90
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +11 -3
- package/docs/API_REFERENCE.md +1 -1
- package/docs/DOCKER.md +5 -1
- package/docs/PERFORMANCE.md +17 -2
- package/docs/README.md +3 -1
- package/docs/SETUP.md +8 -2
- package/package.json +2 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/agent-integrations.ts +9 -3
- package/src/cli/commands/background.ts +34 -2
- package/src/cli/commands/doctor.ts +46 -15
- package/src/cli/commands/hook.ts +13 -1
- package/src/cli/commands/operator-shared.ts +6 -3
- package/src/cli/commands/serve-http.ts +72 -7
- package/src/cli/heap.ts +76 -0
- package/src/cli/invocation.ts +7 -4
- package/src/embedding/api-provider.ts +503 -27
- package/src/embedding/provider.ts +96 -9
- package/src/hooks/handler.ts +9 -18
- package/src/hooks/lifecycle.ts +174 -0
- package/src/memory/observations.ts +68 -18
- package/src/sdk.ts +12 -8
- package/src/store/orama-store.ts +21 -3
|
@@ -58,7 +58,7 @@ An unbound terminal is intentionally project-scoped: it reads and writes project
|
|
|
58
58
|
|
|
59
59
|
Long-term memory is a reviewed layer, not a generic global note store. `memorix memory long-term add` creates a candidate; `qualify` and `approve` are explicit audit transitions before it can appear in a bounded task Workset. A project-derived observation, code fact, Git fact, test, session, Claim, or workflow stays project-bound. Only manual or user-confirmed `user + portable` records can cross local projects for the same local installation identity. When one is task-matched in a Workset, it is intentionally usable background even if its origin differs; use its `durable:<id>` reference with `memorix_detail` only when the full record is needed. Use `archive` or `supersede` when a durable item is no longer current.
|
|
60
60
|
|
|
61
|
-
`--cwd <git-project>` is the canonical way to run direct CLI work from outside a repository. It changes the command's project anchor only; Git remains the source of truth for final project identity.
|
|
61
|
+
`--cwd <git-project>` is the canonical way to run direct CLI work from outside a repository. It changes the command's project anchor only; Git remains the source of truth for final project identity. Existing directories are canonicalized to their real filesystem paths before binding, which prevents macOS aliases such as `/var` and `/private/var` from splitting one project or hiding a Claude local MCP entry.
|
|
62
62
|
|
|
63
63
|
Use MCP when:
|
|
64
64
|
|
|
@@ -164,7 +164,12 @@ Do not assume the HTTP connection alone tells Memorix which project the user mea
|
|
|
164
164
|
|
|
165
165
|
The HTTP service is normally started with `memorix background start`; the same project-binding rules apply when you run `memorix serve-http --port 3211` in the foreground.
|
|
166
166
|
|
|
167
|
-
HTTP MCP sessions idle out after
|
|
167
|
+
HTTP MCP sessions idle out after 12 hours by default. An expired session now
|
|
168
|
+
receives a fast `404` reinitialize hint instead of a generic transport failure.
|
|
169
|
+
If the user's HTTP MCP client needs a longer idle period, set
|
|
170
|
+
`MEMORIX_SESSION_TIMEOUT_MS` before starting or restarting the service.
|
|
171
|
+
Example: `MEMORIX_SESSION_TIMEOUT_MS=86400000` keeps sessions alive for 24
|
|
172
|
+
hours. Set it to `0` only when the host has its own reliable session cleanup.
|
|
168
173
|
|
|
169
174
|
### Do not confuse project config and global config
|
|
170
175
|
|
|
@@ -702,7 +707,10 @@ If it shows "Not running" or "dead":
|
|
|
702
707
|
memorix background ensure
|
|
703
708
|
```
|
|
704
709
|
|
|
705
|
-
If the client is connected but starts failing after
|
|
710
|
+
If the client is connected but starts failing after a long inactive period,
|
|
711
|
+
check for the explicit stale-session `404` reinitialize hint rather than
|
|
712
|
+
treating it as a project-binding failure. The default idle window is 12 hours;
|
|
713
|
+
restart with a longer window only when the client genuinely needs it:
|
|
706
714
|
|
|
707
715
|
```powershell
|
|
708
716
|
$env:MEMORIX_SESSION_TIMEOUT_MS = "86400000"
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -652,7 +652,7 @@ Coordination state is opt-in project state for tasks, handoff messages, locks, a
|
|
|
652
652
|
|
|
653
653
|
Runtime environment:
|
|
654
654
|
|
|
655
|
-
- `MEMORIX_SESSION_TIMEOUT_MS` — HTTP MCP session idle timeout in milliseconds. Default: `
|
|
655
|
+
- `MEMORIX_SESSION_TIMEOUT_MS` — HTTP MCP session idle timeout in milliseconds. Default: `43200000` (12 hours). Expired session IDs receive a fast `404` with a reinitialize hint. Increase this for long-lived clients, for example `86400000` for 24 hours; set `0` only when the host provides reliable lifecycle cleanup.
|
|
656
656
|
|
|
657
657
|
### `team_manage`
|
|
658
658
|
|
package/docs/DOCKER.md
CHANGED
|
@@ -128,7 +128,11 @@ docker compose ps
|
|
|
128
128
|
|
|
129
129
|
### HTTP session idle timeout
|
|
130
130
|
|
|
131
|
-
The HTTP MCP transport defaults to a
|
|
131
|
+
The HTTP MCP transport defaults to a 12-hour idle timeout. The example
|
|
132
|
+
`compose.yaml` raises this to 24 hours for IDEs that keep one HTTP MCP session
|
|
133
|
+
open while the user reads diffs, runs tests, or switches focus. When a session
|
|
134
|
+
does expire, the service returns a fast `404` so the client can initialize a
|
|
135
|
+
fresh MCP session.
|
|
132
136
|
|
|
133
137
|
To change it:
|
|
134
138
|
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -71,13 +71,14 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
71
71
|
|
|
72
72
|
| Knob | Default | Use When |
|
|
73
73
|
| --- | --- | --- |
|
|
74
|
-
| `MEMORIX_SESSION_TIMEOUT_MS` | `
|
|
74
|
+
| `MEMORIX_SESSION_TIMEOUT_MS` | `43200000` (12 h) | Set a shorter GC window for supervised clients, or `0` to disable idle session GC |
|
|
75
75
|
| `MEMORIX_FORMATION_TIMEOUT_MS` | `12000` (12 s) | Raise when LLM-backed formation should outlive slow proxy/provider hops |
|
|
76
76
|
| `MEMORIX_LLM_API_KEY` / `OPENAI_API_KEY` | unset | Enable LLM-backed enrichment, extraction, rerank, or skill generation |
|
|
77
77
|
| `MEMORIX_LLM_TIMEOUT_MS` | `30000` (30 s) | Bound a single LLM-backed extraction/resolve call |
|
|
78
78
|
| `MEMORIX_RERANK_TIMEOUT_MS` | provider default | Bound slow LLM rerank calls |
|
|
79
79
|
| `memorix memory search --quality fast` | n/a | Force a fully local retrieval path for a latency-sensitive call |
|
|
80
80
|
| `npm run benchmark:retrieval -- --records 1000 --runs 100` | n/a | Reproduce hot in-process lexical retrieval latency; not an end-to-end claim |
|
|
81
|
+
| `npm run gate:large-store -- --records 40000` | n/a | Exercise SDK, HTTP MCP, hook persistence, cache integrity, and reopen behavior against a large isolated store |
|
|
81
82
|
| `[codegraph].max_file_bytes` | `2097152` | Raise only when a large file is intentional source that should enter Code Memory |
|
|
82
83
|
| `memorix retention status` | report only | Inspect whether memory growth needs cleanup |
|
|
83
84
|
| `memorix retention archive` | explicit | Archive expired memories when the project gets noisy |
|
|
@@ -86,7 +87,7 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
86
87
|
## Operator Guidance
|
|
87
88
|
|
|
88
89
|
- For memory-only use, prefer stdio MCP or a lightweight `memorix_session_start`; do not join orchestration coordination state by default.
|
|
89
|
-
-
|
|
90
|
+
- HTTP sessions now default to 12 hours and return a fast `404` with a reinitialize hint after expiry. Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable lifecycle cleanup; otherwise retain a bounded GC window.
|
|
90
91
|
- If LLM-backed formation is timing out against a slow proxy/provider, raise `MEMORIX_FORMATION_TIMEOUT_MS` and keep it higher than `MEMORIX_LLM_TIMEOUT_MS`, because the full pipeline can include multiple LLM-backed stages.
|
|
91
92
|
- For Docker, use it when you want a managed HTTP service. Do not use image size alone as the runtime memory estimate.
|
|
92
93
|
- For orchestrated subagent work, expect CPU and disk activity proportional to the spawned agents and verification commands.
|
|
@@ -96,6 +97,20 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
96
97
|
`/api/maintenance` on that local dashboard before assuming a Code Memory scan
|
|
97
98
|
or lifecycle task completed.
|
|
98
99
|
|
|
100
|
+
## Large-Store Release Gate
|
|
101
|
+
|
|
102
|
+
`npm run gate:large-store -- --records 40000` builds Memorix and creates a
|
|
103
|
+
temporary Git project with an isolated data directory. It runs with embeddings
|
|
104
|
+
disabled, so it does not use provider credentials or make paid network calls.
|
|
105
|
+
The gate measures steady-state writes, first lexical search, process memory,
|
|
106
|
+
HTTP MCP and hook persistence, and an SDK close/reopen cycle. It also verifies
|
|
107
|
+
that the personal hook candidate remains durable on disk without becoming
|
|
108
|
+
visible to an unbound SDK reader.
|
|
109
|
+
|
|
110
|
+
Results are machine-specific and should be compared on the same OS and Node
|
|
111
|
+
version. The release gate is an integrity and regression check, not a public
|
|
112
|
+
latency guarantee.
|
|
113
|
+
|
|
99
114
|
## Current Optimization Opportunities
|
|
100
115
|
|
|
101
116
|
These are not release blockers, but they are reasonable future improvements:
|
package/docs/README.md
CHANGED
|
@@ -117,11 +117,13 @@ Historical/deep-reference documents may describe older designs. If they conflict
|
|
|
117
117
|
|
|
118
118
|
## Current Product Line
|
|
119
119
|
|
|
120
|
-
The released product is on the **1.
|
|
120
|
+
The released product is on the **1.6 line**. The current baseline has:
|
|
121
121
|
|
|
122
122
|
- `memorix setup --agent <agent> --global` is the default agent integration command
|
|
123
123
|
- `memorix serve` is the manual stdio MCP server for external agents
|
|
124
124
|
- `memorix background start` runs the shared HTTP MCP service and dashboard
|
|
125
|
+
- context and CodeGraph commands return bounded task receipts before detailed diagnostics
|
|
126
|
+
- HTTP MCP sessions have a bounded, explicit lifecycle and fail with a reinitialize hint after expiry
|
|
125
127
|
- `memorix integrate --agent <agent>` and `memorix hooks install --agent <agent>` remain manual/fallback generation commands
|
|
126
128
|
- `memorix` / `memcode` open memcode, the bundled terminal agent that uses the same Memorix memory pool
|
|
127
129
|
- `~/.memorix/config.toml` and project `memorix.toml` are the user-facing configuration model
|
package/docs/SETUP.md
CHANGED
|
@@ -42,7 +42,7 @@ Memorix project identity comes from Git. Open or initialize a real Git repositor
|
|
|
42
42
|
git init
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
`projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata.
|
|
45
|
+
`projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata. Memorix resolves an existing project directory to its real filesystem path before binding it, so equivalent macOS paths such as `/var/...` and `/private/var/...` do not create separate project or Claude local-MCP entries.
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -247,13 +247,19 @@ Generic HTTP MCP config:
|
|
|
247
247
|
|
|
248
248
|
For multi-project HTTP usage, agents should call `memorix_session_start(projectRoot=...)` with the absolute workspace path when available. This prevents cross-project drift when the background service is shared.
|
|
249
249
|
|
|
250
|
-
HTTP sessions idle out after
|
|
250
|
+
HTTP sessions idle out after 12 hours by default. An expired session receives a
|
|
251
|
+
fast `404` that tells the client to initialize again, rather than being treated
|
|
252
|
+
as a project-binding failure. For clients that intentionally need a longer
|
|
253
|
+
window:
|
|
251
254
|
|
|
252
255
|
```powershell
|
|
253
256
|
$env:MEMORIX_SESSION_TIMEOUT_MS = "86400000"
|
|
254
257
|
memorix background restart
|
|
255
258
|
```
|
|
256
259
|
|
|
260
|
+
Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable
|
|
261
|
+
session cleanup; otherwise keep a bounded idle window.
|
|
262
|
+
|
|
257
263
|
### Option E: Docker HTTP service
|
|
258
264
|
|
|
259
265
|
```bash
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
3
|
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.2",
|
|
5
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"test:e2e": "vitest run tests/e2e/",
|
|
68
68
|
"benchmark:codegraph": "node scripts/benchmark-codegraph-provider.cjs",
|
|
69
69
|
"benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
|
|
70
|
+
"gate:large-store": "npm run build && node scripts/large-store-gate.mjs",
|
|
70
71
|
"generate:star-history": "node scripts/generate-star-history.mjs",
|
|
71
72
|
"sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
|
|
72
73
|
"check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
|
package/server.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/AVIDS2/memorix",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.6.
|
|
10
|
+
"version": "1.6.2",
|
|
11
11
|
"websiteUrl": "https://github.com/AVIDS2/memorix#readme",
|
|
12
12
|
"icons": [
|
|
13
13
|
{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "memorix",
|
|
25
|
-
"version": "1.6.
|
|
25
|
+
"version": "1.6.2",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"packageArguments": [
|
|
28
28
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { basename, dirname, resolve } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -580,11 +580,17 @@ function sanitizeServer(server: MCPServerEntry): AgentMcpCheck['server'] {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
function normalizeProjectKey(projectRoot: string): string {
|
|
583
|
-
return
|
|
583
|
+
return canonicalProjectPath(projectRoot).toLowerCase();
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
function defaultClaudeProjectKey(projectRoot: string): string {
|
|
587
|
-
return
|
|
587
|
+
return canonicalProjectPath(projectRoot);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function canonicalProjectPath(projectRoot: string): string {
|
|
591
|
+
const resolved = resolve(projectRoot);
|
|
592
|
+
const canonical = existsSync(resolved) ? realpathSync(resolved) : resolved;
|
|
593
|
+
return canonical.replace(/\\/g, '/');
|
|
588
594
|
}
|
|
589
595
|
|
|
590
596
|
function getClaudeLocalConfigPath(): string {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
import { defineCommand } from 'citty';
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
22
24
|
import { spawn, execFileSync } from 'node:child_process';
|
|
23
25
|
import { randomBytes } from 'node:crypto';
|
|
24
26
|
import { parseTcpPort } from '../port.js';
|
|
@@ -73,6 +75,33 @@ function parseBackgroundPort(value: string | undefined): number {
|
|
|
73
75
|
return parseTcpPort(value, 3211);
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the CLI file that `background start` should spawn.
|
|
80
|
+
* `process.argv[1]` follows the launcher shim and can point at a different
|
|
81
|
+
* install (for example `$HOME/node_modules/memorix` after `bun add` in $HOME).
|
|
82
|
+
* When this module is bundled into `dist/cli/index.js`, `../index.js` is the
|
|
83
|
+
* library stdio entry (`dist/index.js`) — never spawn that.
|
|
84
|
+
*/
|
|
85
|
+
function resolveBackgroundCliEntryFrom(here: string, argv1: string): string {
|
|
86
|
+
const dir = path.dirname(here);
|
|
87
|
+
if (path.basename(here) === 'index.js' && path.basename(dir) === 'cli') {
|
|
88
|
+
return here;
|
|
89
|
+
}
|
|
90
|
+
const compiledSibling = path.resolve(dir, '..', 'index.js');
|
|
91
|
+
if (fs.existsSync(compiledSibling) && path.basename(path.dirname(compiledSibling)) === 'cli') {
|
|
92
|
+
return compiledSibling;
|
|
93
|
+
}
|
|
94
|
+
return argv1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveBackgroundCliEntry(argv1 = process.argv[1]): string {
|
|
98
|
+
try {
|
|
99
|
+
return resolveBackgroundCliEntryFrom(fileURLToPath(import.meta.url), argv1);
|
|
100
|
+
} catch {
|
|
101
|
+
return argv1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
function parseLogLines(value: string | undefined): number {
|
|
77
106
|
return parseBoundedPositiveInteger(value, 50, 'Log line count', 10_000);
|
|
78
107
|
}
|
|
@@ -343,8 +372,9 @@ export async function doStart(port: number): Promise<void> {
|
|
|
343
372
|
|
|
344
373
|
// 5. Find the memorix CLI entry point
|
|
345
374
|
// We need to spawn `node <cli-entry> serve-http --port <port>`
|
|
346
|
-
//
|
|
347
|
-
|
|
375
|
+
// Prefer this package's compiled CLI so a bun/npm shim cannot spawn a
|
|
376
|
+
// different (often unpatched) memorix from ~/node_modules.
|
|
377
|
+
const cliEntry = resolveBackgroundCliEntry();
|
|
348
378
|
|
|
349
379
|
// 6. Start the service without a visible console. A regular non-detached child
|
|
350
380
|
// can be reclaimed with its parent by terminal hosts on Windows; a detached Node
|
|
@@ -792,4 +822,6 @@ export const _testing = {
|
|
|
792
822
|
parseBackgroundPort,
|
|
793
823
|
parseLogLines,
|
|
794
824
|
formatBackgroundCommandError,
|
|
825
|
+
resolveBackgroundCliEntry,
|
|
826
|
+
resolveBackgroundCliEntryFrom,
|
|
795
827
|
};
|
|
@@ -131,6 +131,7 @@ export default defineCommand({
|
|
|
131
131
|
let bgRunning = false;
|
|
132
132
|
let bgPort = 0;
|
|
133
133
|
let bgPid = 0;
|
|
134
|
+
let backgroundHealthy = false;
|
|
134
135
|
|
|
135
136
|
try {
|
|
136
137
|
if (existsSync(bgStatePath)) {
|
|
@@ -148,7 +149,7 @@ export default defineCommand({
|
|
|
148
149
|
// Health check
|
|
149
150
|
try {
|
|
150
151
|
const http = await import('node:http');
|
|
151
|
-
|
|
152
|
+
backgroundHealthy = await new Promise<boolean>((resolve) => {
|
|
152
153
|
const req = http.request({ hostname: '127.0.0.1', port: bgPort, path: '/api/team', timeout: 3000 }, (res) => {
|
|
153
154
|
res.resume();
|
|
154
155
|
resolve(res.statusCode === 200);
|
|
@@ -157,7 +158,7 @@ export default defineCommand({
|
|
|
157
158
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
158
159
|
req.end();
|
|
159
160
|
});
|
|
160
|
-
if (
|
|
161
|
+
if (backgroundHealthy) {
|
|
161
162
|
lines.push(ok('Health check: OK'));
|
|
162
163
|
} else {
|
|
163
164
|
lines.push(warn('Health check: FAILED (process alive but not responding)'));
|
|
@@ -165,8 +166,9 @@ export default defineCommand({
|
|
|
165
166
|
}
|
|
166
167
|
} catch {
|
|
167
168
|
lines.push(warn('Health check: could not connect'));
|
|
169
|
+
issues.push('Background process is alive but its health endpoint could not be reached. Try "memorix background restart".');
|
|
168
170
|
}
|
|
169
|
-
report.mode = { type: 'background', pid: bgPid, port: bgPort, healthy:
|
|
171
|
+
report.mode = { type: 'background', pid: bgPid, port: bgPort, healthy: backgroundHealthy };
|
|
170
172
|
} else {
|
|
171
173
|
lines.push(info('Background control plane: not running'));
|
|
172
174
|
lines.push(info('Current invocation: CLI (stdio)'));
|
|
@@ -210,20 +212,49 @@ export default defineCommand({
|
|
|
210
212
|
} else {
|
|
211
213
|
lines.push(ok(`Mode: ${mode}`));
|
|
212
214
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
// A running control plane is the source of truth for runtime health.
|
|
216
|
+
// A fresh doctor process must not overwrite a live 401/402/timeout
|
|
217
|
+
// diagnosis with a static "configured = ready" result.
|
|
218
|
+
let controlPlaneEmbedding: {
|
|
219
|
+
status?: string;
|
|
220
|
+
provider?: { name?: string; dimensions?: number };
|
|
221
|
+
failure?: { kind?: string; retryAfterMs?: number };
|
|
222
|
+
} | undefined;
|
|
223
|
+
if (bgRunning) {
|
|
224
|
+
try {
|
|
225
|
+
const response = await fetch(`http://127.0.0.1:${bgPort}/health`, { signal: AbortSignal.timeout(3_000) });
|
|
226
|
+
if (response.ok) {
|
|
227
|
+
const health = await response.json() as { embedding?: typeof controlPlaneEmbedding };
|
|
228
|
+
controlPlaneEmbedding = health.embedding;
|
|
229
|
+
}
|
|
230
|
+
} catch { /* health is advisory; fall back to a local probe */ }
|
|
231
|
+
}
|
|
216
232
|
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
if (controlPlaneEmbedding?.status === 'degraded') {
|
|
234
|
+
const kind = controlPlaneEmbedding.failure?.kind ?? 'upstream';
|
|
235
|
+
const retryAfterMs = controlPlaneEmbedding.failure?.retryAfterMs;
|
|
236
|
+
const retry = typeof retryAfterMs === 'number' && retryAfterMs > 0
|
|
237
|
+
? `; retry in ${Math.ceil(retryAfterMs / 1000)}s`
|
|
238
|
+
: '';
|
|
239
|
+
lines.push(warn(`Status: degraded in the running control plane (${kind}${retry})`));
|
|
240
|
+
lines.push(info('Search: degraded to BM25 fulltext until the provider recovers'));
|
|
241
|
+
issues.push('The running control plane has an unavailable embedding provider. Check provider credentials, billing, or connectivity.');
|
|
242
|
+
report.embedding = { mode, status: 'degraded', failure: kind };
|
|
222
243
|
} else {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
244
|
+
const { getEmbeddingProvider } = await import('../../embedding/provider.js');
|
|
245
|
+
const provider = await getEmbeddingProvider();
|
|
246
|
+
|
|
247
|
+
if (provider) {
|
|
248
|
+
lines.push(ok(`Provider: ${provider.name} (${provider.dimensions}d)`));
|
|
249
|
+
lines.push(ok('Status: ready'));
|
|
250
|
+
lines.push(info('Search: hybrid (BM25 + vector)'));
|
|
251
|
+
report.embedding = { mode, status: 'ready', provider: provider.name, dimensions: provider.dimensions };
|
|
252
|
+
} else {
|
|
253
|
+
lines.push(warn('Status: temporarily unavailable'));
|
|
254
|
+
lines.push(info('Search: degraded to BM25 fulltext (no vector similarity)'));
|
|
255
|
+
issues.push(`Embedding mode is "${mode}" but provider failed to initialize. Check API keys/connectivity.`);
|
|
256
|
+
report.embedding = { mode, status: 'temporarily_unavailable' };
|
|
257
|
+
}
|
|
227
258
|
}
|
|
228
259
|
|
|
229
260
|
// Check cached embeddings count
|
package/src/cli/commands/hook.ts
CHANGED
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
* Entry point called by agent hooks via stdin/stdout.
|
|
5
5
|
* Reads agent's JSON from stdin, normalizes, auto-stores, outputs response.
|
|
6
6
|
*
|
|
7
|
+
* This command is short-lived. A leftover `memorix hook` process is a bug:
|
|
8
|
+
* hosts invoke it on every event, and hung copies exhaust RAM.
|
|
9
|
+
*
|
|
7
10
|
* Usage (called by agent hook configs, not by users directly):
|
|
8
11
|
* memorix hook
|
|
12
|
+
*
|
|
13
|
+
* Env:
|
|
14
|
+
* MEMORIX_HOOK_TIMEOUT_MS — wall-clock budget (default 20000)
|
|
15
|
+
* MEMORIX_HOOK_STDIN_TIMEOUT_MS — stdin idle budget (default 3000)
|
|
16
|
+
* MEMORIX_HOOK_HEAP_MB — V8 heap for this command only (default 512)
|
|
9
17
|
*/
|
|
10
18
|
|
|
11
19
|
import { defineCommand } from 'citty';
|
|
@@ -29,6 +37,10 @@ export default defineCommand({
|
|
|
29
37
|
},
|
|
30
38
|
run: async ({ args }) => {
|
|
31
39
|
const { runHook } = await import('../../hooks/handler.js');
|
|
32
|
-
|
|
40
|
+
const { resolveHookTimeoutMs, runHookWithDeadline } = await import('../../hooks/lifecycle.js');
|
|
41
|
+
await runHookWithDeadline({
|
|
42
|
+
timeoutMs: resolveHookTimeoutMs(),
|
|
43
|
+
run: () => runHook(args.agent as string | undefined, args.event as string | undefined),
|
|
44
|
+
});
|
|
33
45
|
},
|
|
34
46
|
});
|
|
@@ -65,10 +65,13 @@ async function resolveCliProjectContext(options?: CliContextOptions): Promise<{
|
|
|
65
65
|
*/
|
|
66
66
|
export async function getCliReadContext(options?: CliContextOptions): Promise<CliReadContext> {
|
|
67
67
|
const { project, dataDir, invocation } = await resolveCliProjectContext(options);
|
|
68
|
-
await initObservations(dataDir
|
|
68
|
+
await initObservations(dataDir, {
|
|
69
|
+
embeddingWriteMode: 'deferred',
|
|
70
|
+
projectRoot: project.rootPath,
|
|
71
|
+
});
|
|
69
72
|
|
|
70
73
|
if (options?.searchIndex) {
|
|
71
|
-
await prepareSearchIndex();
|
|
74
|
+
await prepareSearchIndex({ skipCachedVectors: true });
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const storedIdentity = await loadCliIdentity(dataDir, project.id);
|
|
@@ -118,7 +121,7 @@ export async function getCliProjectContext(options?: CliContextOptions): Promise
|
|
|
118
121
|
const teamStore = await initTeamStore(dataDir);
|
|
119
122
|
|
|
120
123
|
if (options?.searchIndex) {
|
|
121
|
-
await prepareSearchIndex();
|
|
124
|
+
await prepareSearchIndex({ skipCachedVectors: true });
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
const identity = await resolveCliIdentity({
|