memorix 1.9.3 → 1.9.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/CHANGELOG.md +49 -1
- package/README.md +7 -6
- package/README.zh-CN.md +7 -6
- package/dist/cli/index.js +930 -330
- package/dist/index.d.ts +3 -1
- package/dist/index.js +289 -109
- package/dist/maintenance-runner.js +179 -117
- package/dist/media-audio-runner.js +92 -30
- package/dist/media-video-runner.js +92 -30
- package/dist/memcode-runtime/CHANGELOG.md +49 -1
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +1050 -847
- package/dist/types.d.ts +1 -1
- package/dist/vector-backfill-runner.js +92 -30
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +47 -3
- package/docs/API_REFERENCE.md +3 -1
- package/docs/CONFIGURATION.md +15 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/DOCKER.md +23 -3
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/KNOWN_ISSUES_AND_ROADMAP.md +26 -1
- package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +55 -0
- package/package.json +2 -2
- package/plugins/antigravity/memorix/plugin.json +1 -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/hermes/memorix/plugin.yaml +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/command-guide.ts +2 -2
- package/src/cli/commands/agent-integrations.ts +57 -0
- package/src/cli/commands/background.ts +11 -2
- package/src/cli/commands/media.ts +2 -0
- package/src/cli/commands/serve-http.ts +193 -42
- package/src/cli/commands/setup.ts +77 -10
- package/src/cli/commands/sync-store.ts +47 -12
- package/src/cli/commands/sync.ts +26 -0
- package/src/dashboard/server.ts +10 -12
- package/src/integrations/registry.ts +1 -1
- package/src/media/minimax.ts +26 -2
- package/src/media/video-jobs.ts +9 -1
- package/src/memory/graph.ts +10 -8
- package/src/memory/session.ts +1 -1
- package/src/sdk.ts +24 -0
- package/src/server/mcp-binding-store.ts +14 -3
- package/src/server.ts +5 -3
- package/src/store/file-lock.ts +28 -3
- package/src/store/graph-store.ts +178 -46
- package/src/store/persistence.ts +14 -9
- package/src/store/sqlite-db.ts +6 -3
- package/src/store/sqlite-store.ts +32 -27
- package/src/sync/engine.ts +9 -7
- package/src/sync/namespace.ts +23 -0
- package/src/sync/policy.ts +19 -4
- package/src/sync/store-port.ts +43 -13
- package/src/sync/types.ts +2 -0
- package/src/types.ts +1 -1
- package/src/workspace/mcp-adapters/grok.ts +32 -0
|
@@ -835,18 +835,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
|
|
|
835
835
|
`;
|
|
836
836
|
CREATE_GRAPH_ENTITIES_TABLE = `
|
|
837
837
|
CREATE TABLE IF NOT EXISTS graph_entities (
|
|
838
|
-
|
|
838
|
+
projectId TEXT NOT NULL,
|
|
839
|
+
name TEXT NOT NULL,
|
|
839
840
|
entityType TEXT NOT NULL DEFAULT '',
|
|
840
|
-
observations TEXT NOT NULL DEFAULT '[]'
|
|
841
|
+
observations TEXT NOT NULL DEFAULT '[]',
|
|
842
|
+
PRIMARY KEY (projectId, name)
|
|
841
843
|
);
|
|
842
844
|
`;
|
|
843
845
|
CREATE_GRAPH_RELATIONS_TABLE = `
|
|
844
846
|
CREATE TABLE IF NOT EXISTS graph_relations (
|
|
845
847
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
848
|
+
projectId TEXT NOT NULL,
|
|
846
849
|
from_entity TEXT NOT NULL,
|
|
847
850
|
to_entity TEXT NOT NULL,
|
|
848
851
|
relationType TEXT NOT NULL DEFAULT '',
|
|
849
|
-
UNIQUE(from_entity, to_entity, relationType)
|
|
852
|
+
UNIQUE(projectId, from_entity, to_entity, relationType)
|
|
850
853
|
);
|
|
851
854
|
`;
|
|
852
855
|
CREATE_CODE_FILES_TABLE = `
|
|
@@ -1714,6 +1717,21 @@ var init_sqlite_reliability = __esm({
|
|
|
1714
1717
|
import { promises as fs2 } from "fs";
|
|
1715
1718
|
import { randomUUID } from "crypto";
|
|
1716
1719
|
import path4 from "path";
|
|
1720
|
+
function canReclaimStaleLock(raw) {
|
|
1721
|
+
try {
|
|
1722
|
+
const parsed = JSON.parse(raw);
|
|
1723
|
+
const pid = Number(parsed.pid);
|
|
1724
|
+
if (!Number.isSafeInteger(pid) || pid < 1) return true;
|
|
1725
|
+
try {
|
|
1726
|
+
process.kill(pid, 0);
|
|
1727
|
+
return false;
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
return error.code !== "EPERM";
|
|
1730
|
+
}
|
|
1731
|
+
} catch {
|
|
1732
|
+
return true;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1717
1735
|
async function acquireLock(lockPath) {
|
|
1718
1736
|
for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
|
|
1719
1737
|
try {
|
|
@@ -1727,9 +1745,12 @@ async function acquireLock(lockPath) {
|
|
|
1727
1745
|
try {
|
|
1728
1746
|
const stat5 = await fs2.stat(lockPath);
|
|
1729
1747
|
if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
|
|
1730
|
-
await fs2.
|
|
1731
|
-
|
|
1732
|
-
|
|
1748
|
+
const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
1749
|
+
if (canReclaimStaleLock(raw2)) {
|
|
1750
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
1751
|
+
});
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1733
1754
|
}
|
|
1734
1755
|
} catch {
|
|
1735
1756
|
continue;
|
|
@@ -1740,6 +1761,10 @@ async function acquireLock(lockPath) {
|
|
|
1740
1761
|
}
|
|
1741
1762
|
}
|
|
1742
1763
|
}
|
|
1764
|
+
const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
1765
|
+
if (!canReclaimStaleLock(raw)) {
|
|
1766
|
+
throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
|
|
1767
|
+
}
|
|
1743
1768
|
await fs2.unlink(lockPath).catch(() => {
|
|
1744
1769
|
});
|
|
1745
1770
|
try {
|
|
@@ -5670,6 +5695,7 @@ var init_sqlite_store = __esm({
|
|
|
5670
5695
|
init_sqlite_db();
|
|
5671
5696
|
init_sqlite_reliability();
|
|
5672
5697
|
init_sqlite_fts();
|
|
5698
|
+
init_file_lock();
|
|
5673
5699
|
SqliteBackend = class {
|
|
5674
5700
|
db = null;
|
|
5675
5701
|
dataDir = "";
|
|
@@ -5833,38 +5859,43 @@ var init_sqlite_store = __esm({
|
|
|
5833
5859
|
}
|
|
5834
5860
|
// ── Migration ────────────────────────────────────────────────────
|
|
5835
5861
|
async migrateFromJsonIfNeeded() {
|
|
5836
|
-
const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
|
|
5837
|
-
if (count2.cnt > 0) return;
|
|
5838
5862
|
const jsonPath = path7.join(this.dataDir, "observations.json");
|
|
5839
5863
|
if (!fs4.existsSync(jsonPath)) return;
|
|
5840
|
-
|
|
5864
|
+
await withFileLock(this.dataDir, async () => {
|
|
5865
|
+
const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
|
|
5866
|
+
if (count2.cnt > 0) return;
|
|
5841
5867
|
const raw = fs4.readFileSync(jsonPath, "utf-8");
|
|
5842
|
-
const
|
|
5868
|
+
const parsed = JSON.parse(raw);
|
|
5869
|
+
if (!Array.isArray(parsed)) {
|
|
5870
|
+
throw new Error("[memorix] observations.json migration refused: expected a JSON array");
|
|
5871
|
+
}
|
|
5872
|
+
const observations2 = parsed;
|
|
5843
5873
|
if (observations2.length === 0) return;
|
|
5874
|
+
if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
|
|
5875
|
+
throw new Error("[memorix] observations.json migration refused: invalid observation record");
|
|
5876
|
+
}
|
|
5844
5877
|
console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
|
|
5845
5878
|
const insertMany = this.db.transaction((obsList) => {
|
|
5846
5879
|
for (const obs of obsList) {
|
|
5847
5880
|
this.stmtInsert.run(obsToRow(obs));
|
|
5848
5881
|
}
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
if (fs4.existsSync(counterPath)) {
|
|
5853
|
-
try {
|
|
5882
|
+
const counterPath = path7.join(this.dataDir, "counter.json");
|
|
5883
|
+
let counterNextId;
|
|
5884
|
+
if (fs4.existsSync(counterPath)) {
|
|
5854
5885
|
const counterData = JSON.parse(fs4.readFileSync(counterPath, "utf-8"));
|
|
5855
|
-
const
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5886
|
+
const candidate = counterData.nextId ?? counterData.next_id;
|
|
5887
|
+
if (candidate !== void 0 && (!Number.isSafeInteger(candidate) || Number(candidate) < 1)) {
|
|
5888
|
+
throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
|
|
5889
|
+
}
|
|
5890
|
+
counterNextId = candidate === void 0 ? void 0 : Number(candidate);
|
|
5859
5891
|
}
|
|
5860
|
-
|
|
5861
|
-
this.rawSaveIdCounter(Math.max(
|
|
5862
|
-
|
|
5863
|
-
|
|
5892
|
+
const maxId = Math.max(...obsList.map((obs) => obs.id));
|
|
5893
|
+
this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
|
|
5894
|
+
this.bumpGeneration();
|
|
5895
|
+
});
|
|
5896
|
+
insertMany(observations2);
|
|
5864
5897
|
console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
|
|
5865
|
-
}
|
|
5866
|
-
console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
|
|
5867
|
-
}
|
|
5898
|
+
});
|
|
5868
5899
|
}
|
|
5869
5900
|
// ── Public read ──────────────────────────────────────────────────
|
|
5870
5901
|
async loadAll() {
|
|
@@ -143454,7 +143485,20 @@ function normalizeMiniMaxVideoRequest(input) {
|
|
|
143454
143485
|
if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
|
|
143455
143486
|
throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
|
|
143456
143487
|
}
|
|
143457
|
-
|
|
143488
|
+
let firstFrameImageUrl;
|
|
143489
|
+
if (input.firstFrameImageUrl !== void 0) {
|
|
143490
|
+
firstFrameImageUrl = input.firstFrameImageUrl.trim();
|
|
143491
|
+
let parsed;
|
|
143492
|
+
try {
|
|
143493
|
+
parsed = new URL(firstFrameImageUrl);
|
|
143494
|
+
} catch {
|
|
143495
|
+
throw new Error("MiniMax first-frame image must be a public HTTPS URL");
|
|
143496
|
+
}
|
|
143497
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
|
|
143498
|
+
throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
|
|
143499
|
+
}
|
|
143500
|
+
}
|
|
143501
|
+
return { region, model, prompt, resolution, duration, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
|
|
143458
143502
|
}
|
|
143459
143503
|
function videoRequestConfig(input) {
|
|
143460
143504
|
const normalized = normalizeMiniMaxVideoRequest(input);
|
|
@@ -143531,7 +143575,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
|
|
|
143531
143575
|
headers: videoHeaders(config.apiKey),
|
|
143532
143576
|
body: JSON.stringify({
|
|
143533
143577
|
model: config.model,
|
|
143534
|
-
content: [
|
|
143578
|
+
content: [
|
|
143579
|
+
{ type: "text", text: config.prompt },
|
|
143580
|
+
...config.firstFrameImageUrl ? [{
|
|
143581
|
+
type: "image_url",
|
|
143582
|
+
image_url: { url: config.firstFrameImageUrl },
|
|
143583
|
+
role: "first_frame"
|
|
143584
|
+
}] : []
|
|
143585
|
+
],
|
|
143535
143586
|
resolution: config.resolution,
|
|
143536
143587
|
duration: config.duration,
|
|
143537
143588
|
ratio: config.ratio
|
|
@@ -143692,10 +143743,19 @@ function parseStoredRequest(value) {
|
|
|
143692
143743
|
const duration = value.duration;
|
|
143693
143744
|
const ratio = value.ratio;
|
|
143694
143745
|
const maxBytes = value.maxBytes;
|
|
143695
|
-
if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt !== "string" || resolution !== "2K" || duration !== 5 && duration !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
143746
|
+
if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt !== "string" || value.firstFrameImageUrl !== void 0 && typeof value.firstFrameImageUrl !== "string" || resolution !== "2K" || duration !== 5 && duration !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
143696
143747
|
throw new Error("Media video job has an invalid request payload");
|
|
143697
143748
|
}
|
|
143698
|
-
|
|
143749
|
+
const normalized = normalizeMiniMaxVideoRequest({
|
|
143750
|
+
region,
|
|
143751
|
+
model,
|
|
143752
|
+
prompt,
|
|
143753
|
+
resolution,
|
|
143754
|
+
duration,
|
|
143755
|
+
ratio,
|
|
143756
|
+
firstFrameImageUrl: value.firstFrameImageUrl
|
|
143757
|
+
});
|
|
143758
|
+
return { provider: provider2, ...normalized, maxBytes };
|
|
143699
143759
|
}
|
|
143700
143760
|
function mediaJobIdFromMaintenance(job) {
|
|
143701
143761
|
const mediaJobId = job.payload.mediaJobId;
|
|
@@ -143707,6 +143767,7 @@ function mediaJobIdFromMaintenance(job) {
|
|
|
143707
143767
|
function toVideoRequest(request) {
|
|
143708
143768
|
return {
|
|
143709
143769
|
prompt: request.prompt,
|
|
143770
|
+
firstFrameImageUrl: request.firstFrameImageUrl,
|
|
143710
143771
|
model: request.model,
|
|
143711
143772
|
region: request.region,
|
|
143712
143773
|
resolution: request.resolution,
|
|
@@ -143798,6 +143859,7 @@ function queueMiniMaxVideoGeneration(input) {
|
|
|
143798
143859
|
region: normalized.region,
|
|
143799
143860
|
model: normalized.model,
|
|
143800
143861
|
prompt: normalized.prompt,
|
|
143862
|
+
...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
|
|
143801
143863
|
resolution: normalized.resolution,
|
|
143802
143864
|
duration: normalized.duration,
|
|
143803
143865
|
ratio: normalized.ratio,
|
|
@@ -2,7 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [1.9.5] - 2026-09-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Setup HTTP MCP** -- `memorix setup --mcp http` honors Grok (writes a
|
|
9
|
+
`[mcp_servers.memorix]` URL in Grok `config.toml`) and rewrites installed
|
|
10
|
+
plugin/extension bundled MCP manifests from `memorix serve` to
|
|
11
|
+
`http://localhost:3211/mcp`. Default Grok MCP stays host-owned. Docs include
|
|
12
|
+
generic systemd user unit and LaunchAgent KeepAlive examples for
|
|
13
|
+
`memorix serve-http`.
|
|
14
|
+
- **User-global store sync** -- `memorix sync store push|pull|status --scope user`
|
|
15
|
+
replicates every local project's eligible observations through the existing
|
|
16
|
+
opt-in relay. Default `--scope project` is unchanged. Privacy filters still
|
|
17
|
+
drop personal, agent-targeted, candidate, and ephemeral rows. The live
|
|
18
|
+
SQLite file is never copied.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **HTTP control-plane safety** -- JSON request bodies are bounded, non-loopback
|
|
22
|
+
binds require an explicit Bearer token or unsafe opt-in, listen failures are
|
|
23
|
+
reported synchronously, background startup removes unverified state, and
|
|
24
|
+
shutdown waits for HTTP and SQLite resources to close.
|
|
25
|
+
- **Project-scoped graph storage** -- graph entities and relations now carry a
|
|
26
|
+
project key, legacy JSONL migration recovers ownership from observation
|
|
27
|
+
references, and Dashboard/MCP graph access no longer crosses projects.
|
|
28
|
+
- **Migration and lifecycle reliability** -- legacy observation/subdirectory
|
|
29
|
+
migrations use locks and atomic writes, malformed observation JSON fails
|
|
30
|
+
closed, stale file locks cannot evict a live writer, stateless MCP bindings
|
|
31
|
+
expire and are cleaned up, and SDK clients share resources safely.
|
|
32
|
+
- **User-scope sync correctness** -- imported observation IDs are remapped on
|
|
33
|
+
collision, tombstones validate their source project, and user namespaces
|
|
34
|
+
must be explicit per-user values.
|
|
35
|
+
- **Release contract** -- publish workflow validates tag/version/commit
|
|
36
|
+
identity, waits for npm visibility before MCP Registry publication, and
|
|
37
|
+
keeps prepublish checks non-mutating.
|
|
38
|
+
|
|
39
|
+
## [1.9.4] - 2026-09-18
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **MiniMax first-frame video input** -- image-to-video generation now accepts
|
|
43
|
+
a public HTTPS first-frame image URL through the CLI and MCP surfaces,
|
|
44
|
+
preserves it in durable media jobs, and submits it with the provider's
|
|
45
|
+
`first_frame` image role.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- **First-frame URL safety** -- rejects non-HTTPS URLs and embedded credentials
|
|
49
|
+
before a media request is submitted or persisted.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- **Grok Build documentation** -- the README comparison now uses the official
|
|
53
|
+
Grok Build logo assets.
|
|
6
54
|
|
|
7
55
|
## [1.9.3] - 2026-09-14
|
|
8
56
|
|
package/dist/sdk.d.ts
CHANGED
|
@@ -120,6 +120,7 @@ declare class MemoryClient {
|
|
|
120
120
|
private _oramaStore;
|
|
121
121
|
private _obsStore;
|
|
122
122
|
private _freshness;
|
|
123
|
+
private _sdkRefRegistered;
|
|
123
124
|
/** @internal — use createMemoryClient() instead */
|
|
124
125
|
constructor(projectId: string, projectRoot: string, dataDir: string);
|
|
125
126
|
/** The canonical project ID (derived from Git remote or local path) */
|