memorix 1.2.7 → 1.2.9
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 +15 -0
- package/dist/cli/index.js +19531 -19017
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +7 -2
- package/dist/index.js +7870 -7497
- package/dist/index.js.map +1 -1
- package/dist/maintenance-jobs-o1rYSFcM.d.ts +36 -0
- package/dist/maintenance-runner.d.ts +1 -28
- package/dist/maintenance-runner.js +5937 -5596
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +15 -0
- package/dist/sdk.js +7866 -7493
- package/dist/sdk.js.map +1 -1
- package/dist/vector-backfill-runner.d.ts +31 -0
- package/dist/vector-backfill-runner.js +12670 -0
- package/dist/vector-backfill-runner.js.map +1 -0
- package/docs/dev-log/progress.txt +25 -0
- package/package.json +3 -3
- package/plugins/claude/memorix/.claude-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/src/cli/commands/agent-integrations.ts +102 -5
- package/src/cli/commands/operator-shared.ts +4 -1
- package/src/cli/commands/serve-http.ts +58 -50
- package/src/cli/commands/setup.ts +44 -0
- package/src/dashboard/server.ts +54 -63
- package/src/memory/observations.ts +175 -99
- package/src/memory/retention.ts +106 -28
- package/src/memory/session.ts +71 -12
- package/src/runtime/maintenance-jobs.ts +84 -4
- package/src/runtime/project-maintenance.ts +63 -6
- package/src/runtime/vector-backfill-runner.ts +144 -0
- package/src/search/intent-detector.ts +39 -1
- package/src/server.ts +5 -5
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { loadDotenv } from '../config/dotenv-loader.js';
|
|
7
|
+
import { initProjectRoot } from '../config/yaml-loader.js';
|
|
8
|
+
import { prepareSearchIndex, initObservations } from '../memory/observations.js';
|
|
9
|
+
import { sanitizeCredentials } from '../memory/secret-filter.js';
|
|
10
|
+
import { initObservationStore } from '../store/obs-store.js';
|
|
11
|
+
import { getDeferredCachedVectorHydration } from '../store/orama-store.js';
|
|
12
|
+
import { closeAllDatabases } from '../store/sqlite-db.js';
|
|
13
|
+
import { MaintenanceJobStore, MaintenanceJobWorker } from './maintenance-jobs.js';
|
|
14
|
+
import { createProjectMaintenanceHandler } from './project-maintenance.js';
|
|
15
|
+
|
|
16
|
+
export interface VectorBackfillRequest {
|
|
17
|
+
projectId: string;
|
|
18
|
+
projectRoot: string;
|
|
19
|
+
dataDir: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VectorBackfillLauncherOptions {
|
|
23
|
+
runnerPath?: string;
|
|
24
|
+
exists?: (path: string) => boolean;
|
|
25
|
+
spawn?: typeof spawn;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
29
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Resolve the standalone worker next to either the library or CLI bundle. */
|
|
33
|
+
export function resolveVectorBackfillRunnerPath(moduleUrl = import.meta.url): string {
|
|
34
|
+
const moduleDir = path.dirname(fileURLToPath(moduleUrl));
|
|
35
|
+
const distDir = path.basename(moduleDir) === 'cli'
|
|
36
|
+
? path.dirname(moduleDir)
|
|
37
|
+
: path.basename(moduleDir) === 'runtime' && path.basename(path.dirname(moduleDir)) === 'src'
|
|
38
|
+
? path.join(path.dirname(path.dirname(moduleDir)), 'dist')
|
|
39
|
+
: moduleDir;
|
|
40
|
+
return path.join(distDir, 'vector-backfill-runner.js');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse the internal request passed from a short-lived CLI process. */
|
|
44
|
+
export function parseVectorBackfillRequest(raw: string): VectorBackfillRequest {
|
|
45
|
+
let value: unknown;
|
|
46
|
+
try {
|
|
47
|
+
value = JSON.parse(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
throw new Error('Vector backfill runner received invalid JSON input');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
!value ||
|
|
54
|
+
typeof value !== 'object' ||
|
|
55
|
+
!isNonEmptyString((value as VectorBackfillRequest).projectId) ||
|
|
56
|
+
!isNonEmptyString((value as VectorBackfillRequest).projectRoot) ||
|
|
57
|
+
!isNonEmptyString((value as VectorBackfillRequest).dataDir) ||
|
|
58
|
+
!path.isAbsolute((value as VectorBackfillRequest).projectRoot) ||
|
|
59
|
+
!path.isAbsolute((value as VectorBackfillRequest).dataDir)
|
|
60
|
+
) {
|
|
61
|
+
throw new Error('Vector backfill runner received an invalid request');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
projectId: (value as VectorBackfillRequest).projectId,
|
|
66
|
+
projectRoot: (value as VectorBackfillRequest).projectRoot,
|
|
67
|
+
dataDir: (value as VectorBackfillRequest).dataDir,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Start a detached one-shot worker. The caller has already persisted the
|
|
73
|
+
* observation and durable vector job, so failure to start is recoverable by a
|
|
74
|
+
* later MCP or control-plane session.
|
|
75
|
+
*/
|
|
76
|
+
export function launchDetachedVectorBackfill(
|
|
77
|
+
request: VectorBackfillRequest,
|
|
78
|
+
options: VectorBackfillLauncherOptions = {},
|
|
79
|
+
): boolean {
|
|
80
|
+
const runnerPath = options.runnerPath ?? resolveVectorBackfillRunnerPath();
|
|
81
|
+
const exists = options.exists ?? existsSync;
|
|
82
|
+
if (!exists(runnerPath)) return false;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const child = (options.spawn ?? spawn)(process.execPath, [runnerPath], {
|
|
86
|
+
cwd: request.projectRoot,
|
|
87
|
+
detached: true,
|
|
88
|
+
stdio: 'ignore',
|
|
89
|
+
// The request contains only local project metadata, never credentials.
|
|
90
|
+
// Environment transport avoids a live stdin pipe keeping the CLI alive.
|
|
91
|
+
env: { ...process.env, MEMORIX_VECTOR_BACKFILL_REQUEST: JSON.stringify(request) },
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
}) as ChildProcess;
|
|
94
|
+
child.once?.('error', () => {});
|
|
95
|
+
child.unref();
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Run one durable vector-backfill job without sharing the CLI event loop. */
|
|
103
|
+
export async function executeVectorBackfill(request: VectorBackfillRequest) {
|
|
104
|
+
initProjectRoot(request.projectRoot);
|
|
105
|
+
loadDotenv(request.projectRoot);
|
|
106
|
+
await initObservationStore(request.dataDir);
|
|
107
|
+
await initObservations(request.dataDir);
|
|
108
|
+
await prepareSearchIndex();
|
|
109
|
+
await getDeferredCachedVectorHydration()?.catch(() => {});
|
|
110
|
+
|
|
111
|
+
const worker = new MaintenanceJobWorker(
|
|
112
|
+
new MaintenanceJobStore(request.dataDir),
|
|
113
|
+
createProjectMaintenanceHandler(request.projectId, request.dataDir, request.projectRoot),
|
|
114
|
+
{ projectId: request.projectId, kinds: ['vector-backfill'] },
|
|
115
|
+
);
|
|
116
|
+
return worker.runOnce();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function readStdin(): Promise<string> {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
let raw = '';
|
|
122
|
+
process.stdin.setEncoding('utf8');
|
|
123
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
124
|
+
process.stdin.once('error', reject);
|
|
125
|
+
process.stdin.once('end', () => resolve(raw));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function main(): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
const raw = process.env.MEMORIX_VECTOR_BACKFILL_REQUEST ?? await readStdin();
|
|
132
|
+
await executeVectorBackfill(parseVectorBackfillRequest(raw));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const detail = sanitizeCredentials(error instanceof Error ? error.message : String(error));
|
|
135
|
+
process.stderr.write(`[memorix] vector backfill worker failed: ${detail}\n`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
} finally {
|
|
138
|
+
closeAllDatabases();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (process.argv[1] && process.argv[1].endsWith('vector-backfill-runner.js')) {
|
|
143
|
+
void main();
|
|
144
|
+
}
|
|
@@ -12,7 +12,7 @@ import type { ObservationType } from '../types.js';
|
|
|
12
12
|
|
|
13
13
|
// ─── Types ───
|
|
14
14
|
|
|
15
|
-
export type QueryIntent = 'why' | 'when' | 'how' | 'what_changed' | 'problem' | 'general';
|
|
15
|
+
export type QueryIntent = 'why' | 'when' | 'how' | 'what_changed' | 'problem' | 'state' | 'general';
|
|
16
16
|
|
|
17
17
|
export interface IntentResult {
|
|
18
18
|
/** Detected intent category */
|
|
@@ -39,6 +39,30 @@ interface IntentPattern {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const INTENT_PATTERNS: IntentPattern[] = [
|
|
42
|
+
{
|
|
43
|
+
// Resumption queries — "where were we", "what's left". Weighted just above the
|
|
44
|
+
// others because these are asked at handoff time, when returning the current
|
|
45
|
+
// state matters more than returning the best semantic match.
|
|
46
|
+
intent: 'state',
|
|
47
|
+
patterns: [
|
|
48
|
+
/\bprogress\b/i,
|
|
49
|
+
/\bstatus\b/i,
|
|
50
|
+
/\bremaining\b/i,
|
|
51
|
+
/\bnext steps?\b/i,
|
|
52
|
+
/\bwhere (?:did|are) we\b/i,
|
|
53
|
+
/\bpick(?:ing)? up\b/i,
|
|
54
|
+
/\bresume\b/i,
|
|
55
|
+
/\bto-?do\b/i,
|
|
56
|
+
/进度/,
|
|
57
|
+
/做到哪/,
|
|
58
|
+
/下一步/,
|
|
59
|
+
/待办/,
|
|
60
|
+
/剩下/,
|
|
61
|
+
/继续/,
|
|
62
|
+
/接着做/,
|
|
63
|
+
],
|
|
64
|
+
weight: 1.1,
|
|
65
|
+
},
|
|
42
66
|
{
|
|
43
67
|
intent: 'why',
|
|
44
68
|
patterns: [
|
|
@@ -139,6 +163,12 @@ const INTENT_PATTERNS: IntentPattern[] = [
|
|
|
139
163
|
// ─── Type Boost Maps ───
|
|
140
164
|
|
|
141
165
|
const INTENT_TYPE_BOOSTS: Record<QueryIntent, Partial<Record<ObservationType, number>>> = {
|
|
166
|
+
state: {
|
|
167
|
+
'session-request': 2.5,
|
|
168
|
+
'what-changed': 2.0,
|
|
169
|
+
'discovery': 1.5,
|
|
170
|
+
'problem-solution': 1.2,
|
|
171
|
+
},
|
|
142
172
|
why: {
|
|
143
173
|
'decision': 3.0,
|
|
144
174
|
'why-it-exists': 3.0,
|
|
@@ -198,6 +228,14 @@ const INTENT_SOURCE_BOOSTS: Partial<Record<QueryIntent, Partial<Record<'agent' |
|
|
|
198
228
|
};
|
|
199
229
|
|
|
200
230
|
const INTENT_FIELD_BOOSTS: Partial<Record<QueryIntent, Record<string, number>>> = {
|
|
231
|
+
state: {
|
|
232
|
+
title: 3, // State notes are titled by topic — the title carries the signal
|
|
233
|
+
entityName: 1.5,
|
|
234
|
+
narrative: 2,
|
|
235
|
+
facts: 2.5, // Progress lines usually live in facts
|
|
236
|
+
concepts: 1,
|
|
237
|
+
filesModified: 0.5,
|
|
238
|
+
},
|
|
201
239
|
why: {
|
|
202
240
|
title: 2,
|
|
203
241
|
entityName: 1.5,
|
package/src/server.ts
CHANGED
|
@@ -3637,7 +3637,7 @@ export async function createMemorixServer(
|
|
|
3637
3637
|
/**
|
|
3638
3638
|
* memorix_session_start — Start a new coding session
|
|
3639
3639
|
*
|
|
3640
|
-
* Creates a session record and returns
|
|
3640
|
+
* Creates a session record and returns a compact continuation card.
|
|
3641
3641
|
* This is the entry point for session-aware memory management.
|
|
3642
3642
|
*/
|
|
3643
3643
|
server.registerTool(
|
|
@@ -3645,10 +3645,10 @@ export async function createMemorixServer(
|
|
|
3645
3645
|
{
|
|
3646
3646
|
title: 'Start Session',
|
|
3647
3647
|
description:
|
|
3648
|
-
'Start a new coding session. Returns
|
|
3649
|
-
'Call this at the beginning of a session to track activity
|
|
3648
|
+
'Start a new coding session. Returns a compact continuation card with the latest handoff and a few memory references. ' +
|
|
3649
|
+
'Call this at the beginning of a session to track activity; retrieve a referenced memory only when it is relevant. ' +
|
|
3650
3650
|
'Any previous active session for this project will be auto-closed. ' +
|
|
3651
|
-
'By default this is lightweight: it binds the project, opens a session, and
|
|
3651
|
+
'By default this is lightweight: it binds the project, opens a session, and avoids dumping full history into the new context. ' +
|
|
3652
3652
|
'Coordination identity is opt-in via `joinTeam: true` or a separate `team_manage` join call.\n\n' +
|
|
3653
3653
|
'IMPORTANT for HTTP/control-plane mode: pass `projectRoot` with the absolute path to your ' +
|
|
3654
3654
|
'workspace root (e.g., the directory open in your IDE). Memorix uses this to detect the git ' +
|
|
@@ -3851,7 +3851,7 @@ export async function createMemorixServer(
|
|
|
3851
3851
|
} catch { /* mini-skills not available yet — skip */ }
|
|
3852
3852
|
|
|
3853
3853
|
if (result.previousContext) {
|
|
3854
|
-
lines.push('---', '[TASK] **
|
|
3854
|
+
lines.push('---', '[TASK] **Continuation card:**', '', result.previousContext);
|
|
3855
3855
|
} else {
|
|
3856
3856
|
lines.push('No previous session context found. This appears to be a fresh project.');
|
|
3857
3857
|
}
|