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
|
@@ -643,18 +643,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
|
|
|
643
643
|
`;
|
|
644
644
|
CREATE_GRAPH_ENTITIES_TABLE = `
|
|
645
645
|
CREATE TABLE IF NOT EXISTS graph_entities (
|
|
646
|
-
|
|
646
|
+
projectId TEXT NOT NULL,
|
|
647
|
+
name TEXT NOT NULL,
|
|
647
648
|
entityType TEXT NOT NULL DEFAULT '',
|
|
648
|
-
observations TEXT NOT NULL DEFAULT '[]'
|
|
649
|
+
observations TEXT NOT NULL DEFAULT '[]',
|
|
650
|
+
PRIMARY KEY (projectId, name)
|
|
649
651
|
);
|
|
650
652
|
`;
|
|
651
653
|
CREATE_GRAPH_RELATIONS_TABLE = `
|
|
652
654
|
CREATE TABLE IF NOT EXISTS graph_relations (
|
|
653
655
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
656
|
+
projectId TEXT NOT NULL,
|
|
654
657
|
from_entity TEXT NOT NULL,
|
|
655
658
|
to_entity TEXT NOT NULL,
|
|
656
659
|
relationType TEXT NOT NULL DEFAULT '',
|
|
657
|
-
UNIQUE(from_entity, to_entity, relationType)
|
|
660
|
+
UNIQUE(projectId, from_entity, to_entity, relationType)
|
|
658
661
|
);
|
|
659
662
|
`;
|
|
660
663
|
CREATE_CODE_FILES_TABLE = `
|
|
@@ -3041,13 +3044,120 @@ var init_sqlite_reliability = __esm({
|
|
|
3041
3044
|
}
|
|
3042
3045
|
});
|
|
3043
3046
|
|
|
3047
|
+
// src/store/file-lock.ts
|
|
3048
|
+
import { promises as fs2 } from "fs";
|
|
3049
|
+
import { randomUUID } from "crypto";
|
|
3050
|
+
import path5 from "path";
|
|
3051
|
+
function canReclaimStaleLock(raw) {
|
|
3052
|
+
try {
|
|
3053
|
+
const parsed = JSON.parse(raw);
|
|
3054
|
+
const pid = Number(parsed.pid);
|
|
3055
|
+
if (!Number.isSafeInteger(pid) || pid < 1) return true;
|
|
3056
|
+
try {
|
|
3057
|
+
process.kill(pid, 0);
|
|
3058
|
+
return false;
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
return error.code !== "EPERM";
|
|
3061
|
+
}
|
|
3062
|
+
} catch {
|
|
3063
|
+
return true;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
async function acquireLock(lockPath) {
|
|
3067
|
+
for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
|
|
3068
|
+
try {
|
|
3069
|
+
const fd = await fs2.open(lockPath, "wx");
|
|
3070
|
+
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3071
|
+
await fd.close();
|
|
3072
|
+
return;
|
|
3073
|
+
} catch (err) {
|
|
3074
|
+
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
3075
|
+
if (code === "EEXIST" || code === "EPERM") {
|
|
3076
|
+
try {
|
|
3077
|
+
const stat5 = await fs2.stat(lockPath);
|
|
3078
|
+
if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
|
|
3079
|
+
const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
3080
|
+
if (canReclaimStaleLock(raw2)) {
|
|
3081
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
3082
|
+
});
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
} catch {
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
await new Promise((r2) => setTimeout(r2, RETRY_INTERVAL_MS));
|
|
3090
|
+
} else {
|
|
3091
|
+
throw err;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
3096
|
+
if (!canReclaimStaleLock(raw)) {
|
|
3097
|
+
throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
|
|
3098
|
+
}
|
|
3099
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
3100
|
+
});
|
|
3101
|
+
try {
|
|
3102
|
+
const fd = await fs2.open(lockPath, "wx");
|
|
3103
|
+
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3104
|
+
await fd.close();
|
|
3105
|
+
return;
|
|
3106
|
+
} catch {
|
|
3107
|
+
throw new Error(`Failed to acquire lock: ${lockPath} (timeout after ${MAX_RETRIES * RETRY_INTERVAL_MS}ms)`);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
async function releaseLock(lockPath) {
|
|
3111
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
async function withFileLock(projectDir2, fn) {
|
|
3115
|
+
const lockPath = path5.join(projectDir2, ".memorix.lock");
|
|
3116
|
+
await acquireLock(lockPath);
|
|
3117
|
+
try {
|
|
3118
|
+
return await fn();
|
|
3119
|
+
} finally {
|
|
3120
|
+
await releaseLock(lockPath);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
async function atomicWriteFileOnce(filePath, data) {
|
|
3124
|
+
const tmpPath = filePath + `.tmp.${process.pid}.${randomUUID()}`;
|
|
3125
|
+
try {
|
|
3126
|
+
await fs2.writeFile(tmpPath, data, "utf-8");
|
|
3127
|
+
await fs2.rename(tmpPath, filePath);
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
await fs2.unlink(tmpPath).catch(() => {
|
|
3130
|
+
});
|
|
3131
|
+
throw error;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
function atomicWriteFile(filePath, data) {
|
|
3135
|
+
const previous = atomicWriteTails.get(filePath) ?? Promise.resolve();
|
|
3136
|
+
const next = previous.catch(() => void 0).then(() => atomicWriteFileOnce(filePath, data));
|
|
3137
|
+
atomicWriteTails.set(filePath, next);
|
|
3138
|
+
return next.finally(() => {
|
|
3139
|
+
if (atomicWriteTails.get(filePath) === next) atomicWriteTails.delete(filePath);
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
var LOCK_STALE_MS, RETRY_INTERVAL_MS, MAX_RETRIES, atomicWriteTails;
|
|
3143
|
+
var init_file_lock = __esm({
|
|
3144
|
+
"src/store/file-lock.ts"() {
|
|
3145
|
+
"use strict";
|
|
3146
|
+
init_esm_shims();
|
|
3147
|
+
LOCK_STALE_MS = 1e4;
|
|
3148
|
+
RETRY_INTERVAL_MS = 50;
|
|
3149
|
+
MAX_RETRIES = 60;
|
|
3150
|
+
atomicWriteTails = /* @__PURE__ */ new Map();
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
|
|
3044
3154
|
// src/store/sqlite-store.ts
|
|
3045
3155
|
var sqlite_store_exports = {};
|
|
3046
3156
|
__export(sqlite_store_exports, {
|
|
3047
3157
|
SqliteBackend: () => SqliteBackend
|
|
3048
3158
|
});
|
|
3049
|
-
import
|
|
3050
|
-
import
|
|
3159
|
+
import path6 from "path";
|
|
3160
|
+
import fs3 from "fs";
|
|
3051
3161
|
function obsToRow(obs) {
|
|
3052
3162
|
return {
|
|
3053
3163
|
id: obs.id,
|
|
@@ -3134,6 +3244,7 @@ var init_sqlite_store = __esm({
|
|
|
3134
3244
|
init_sqlite_db();
|
|
3135
3245
|
init_sqlite_reliability();
|
|
3136
3246
|
init_sqlite_fts();
|
|
3247
|
+
init_file_lock();
|
|
3137
3248
|
SqliteBackend = class {
|
|
3138
3249
|
db = null;
|
|
3139
3250
|
dataDir = "";
|
|
@@ -3297,38 +3408,43 @@ var init_sqlite_store = __esm({
|
|
|
3297
3408
|
}
|
|
3298
3409
|
// ── Migration ────────────────────────────────────────────────────
|
|
3299
3410
|
async migrateFromJsonIfNeeded() {
|
|
3300
|
-
const
|
|
3301
|
-
if (
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
const raw =
|
|
3306
|
-
const
|
|
3411
|
+
const jsonPath = path6.join(this.dataDir, "observations.json");
|
|
3412
|
+
if (!fs3.existsSync(jsonPath)) return;
|
|
3413
|
+
await withFileLock(this.dataDir, async () => {
|
|
3414
|
+
const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
|
|
3415
|
+
if (count2.cnt > 0) return;
|
|
3416
|
+
const raw = fs3.readFileSync(jsonPath, "utf-8");
|
|
3417
|
+
const parsed = JSON.parse(raw);
|
|
3418
|
+
if (!Array.isArray(parsed)) {
|
|
3419
|
+
throw new Error("[memorix] observations.json migration refused: expected a JSON array");
|
|
3420
|
+
}
|
|
3421
|
+
const observations2 = parsed;
|
|
3307
3422
|
if (observations2.length === 0) return;
|
|
3423
|
+
if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
|
|
3424
|
+
throw new Error("[memorix] observations.json migration refused: invalid observation record");
|
|
3425
|
+
}
|
|
3308
3426
|
console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
|
|
3309
3427
|
const insertMany = this.db.transaction((obsList) => {
|
|
3310
3428
|
for (const obs of obsList) {
|
|
3311
3429
|
this.stmtInsert.run(obsToRow(obs));
|
|
3312
3430
|
}
|
|
3431
|
+
const counterPath = path6.join(this.dataDir, "counter.json");
|
|
3432
|
+
let counterNextId;
|
|
3433
|
+
if (fs3.existsSync(counterPath)) {
|
|
3434
|
+
const counterData = JSON.parse(fs3.readFileSync(counterPath, "utf-8"));
|
|
3435
|
+
const candidate = counterData.nextId ?? counterData.next_id;
|
|
3436
|
+
if (candidate !== void 0 && (!Number.isSafeInteger(candidate) || Number(candidate) < 1)) {
|
|
3437
|
+
throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
|
|
3438
|
+
}
|
|
3439
|
+
counterNextId = candidate === void 0 ? void 0 : Number(candidate);
|
|
3440
|
+
}
|
|
3441
|
+
const maxId = Math.max(...obsList.map((obs) => obs.id));
|
|
3442
|
+
this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
|
|
3443
|
+
this.bumpGeneration();
|
|
3313
3444
|
});
|
|
3314
3445
|
insertMany(observations2);
|
|
3315
|
-
const counterPath = path5.join(this.dataDir, "counter.json");
|
|
3316
|
-
if (fs2.existsSync(counterPath)) {
|
|
3317
|
-
try {
|
|
3318
|
-
const counterData = JSON.parse(fs2.readFileSync(counterPath, "utf-8"));
|
|
3319
|
-
const nextId2 = counterData.nextId ?? counterData.next_id ?? Math.max(...observations2.map((o) => o.id)) + 1;
|
|
3320
|
-
this.rawSaveIdCounter(nextId2);
|
|
3321
|
-
} catch {
|
|
3322
|
-
this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
|
|
3323
|
-
}
|
|
3324
|
-
} else {
|
|
3325
|
-
this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
|
|
3326
|
-
}
|
|
3327
|
-
this.bumpGeneration();
|
|
3328
3446
|
console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
|
|
3329
|
-
}
|
|
3330
|
-
console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
|
|
3331
|
-
}
|
|
3447
|
+
});
|
|
3332
3448
|
}
|
|
3333
3449
|
// ── Public read ──────────────────────────────────────────────────
|
|
3334
3450
|
async loadAll() {
|
|
@@ -3657,91 +3773,6 @@ var init_types = __esm({
|
|
|
3657
3773
|
}
|
|
3658
3774
|
});
|
|
3659
3775
|
|
|
3660
|
-
// src/store/file-lock.ts
|
|
3661
|
-
import { promises as fs3 } from "fs";
|
|
3662
|
-
import { randomUUID } from "crypto";
|
|
3663
|
-
import path6 from "path";
|
|
3664
|
-
async function acquireLock(lockPath) {
|
|
3665
|
-
for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
|
|
3666
|
-
try {
|
|
3667
|
-
const fd = await fs3.open(lockPath, "wx");
|
|
3668
|
-
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3669
|
-
await fd.close();
|
|
3670
|
-
return;
|
|
3671
|
-
} catch (err) {
|
|
3672
|
-
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
3673
|
-
if (code === "EEXIST" || code === "EPERM") {
|
|
3674
|
-
try {
|
|
3675
|
-
const stat5 = await fs3.stat(lockPath);
|
|
3676
|
-
if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
|
|
3677
|
-
await fs3.unlink(lockPath).catch(() => {
|
|
3678
|
-
});
|
|
3679
|
-
continue;
|
|
3680
|
-
}
|
|
3681
|
-
} catch {
|
|
3682
|
-
continue;
|
|
3683
|
-
}
|
|
3684
|
-
await new Promise((r2) => setTimeout(r2, RETRY_INTERVAL_MS));
|
|
3685
|
-
} else {
|
|
3686
|
-
throw err;
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
}
|
|
3690
|
-
await fs3.unlink(lockPath).catch(() => {
|
|
3691
|
-
});
|
|
3692
|
-
try {
|
|
3693
|
-
const fd = await fs3.open(lockPath, "wx");
|
|
3694
|
-
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3695
|
-
await fd.close();
|
|
3696
|
-
return;
|
|
3697
|
-
} catch {
|
|
3698
|
-
throw new Error(`Failed to acquire lock: ${lockPath} (timeout after ${MAX_RETRIES * RETRY_INTERVAL_MS}ms)`);
|
|
3699
|
-
}
|
|
3700
|
-
}
|
|
3701
|
-
async function releaseLock(lockPath) {
|
|
3702
|
-
await fs3.unlink(lockPath).catch(() => {
|
|
3703
|
-
});
|
|
3704
|
-
}
|
|
3705
|
-
async function withFileLock(projectDir2, fn) {
|
|
3706
|
-
const lockPath = path6.join(projectDir2, ".memorix.lock");
|
|
3707
|
-
await acquireLock(lockPath);
|
|
3708
|
-
try {
|
|
3709
|
-
return await fn();
|
|
3710
|
-
} finally {
|
|
3711
|
-
await releaseLock(lockPath);
|
|
3712
|
-
}
|
|
3713
|
-
}
|
|
3714
|
-
async function atomicWriteFileOnce(filePath, data) {
|
|
3715
|
-
const tmpPath = filePath + `.tmp.${process.pid}.${randomUUID()}`;
|
|
3716
|
-
try {
|
|
3717
|
-
await fs3.writeFile(tmpPath, data, "utf-8");
|
|
3718
|
-
await fs3.rename(tmpPath, filePath);
|
|
3719
|
-
} catch (error) {
|
|
3720
|
-
await fs3.unlink(tmpPath).catch(() => {
|
|
3721
|
-
});
|
|
3722
|
-
throw error;
|
|
3723
|
-
}
|
|
3724
|
-
}
|
|
3725
|
-
function atomicWriteFile(filePath, data) {
|
|
3726
|
-
const previous = atomicWriteTails.get(filePath) ?? Promise.resolve();
|
|
3727
|
-
const next = previous.catch(() => void 0).then(() => atomicWriteFileOnce(filePath, data));
|
|
3728
|
-
atomicWriteTails.set(filePath, next);
|
|
3729
|
-
return next.finally(() => {
|
|
3730
|
-
if (atomicWriteTails.get(filePath) === next) atomicWriteTails.delete(filePath);
|
|
3731
|
-
});
|
|
3732
|
-
}
|
|
3733
|
-
var LOCK_STALE_MS, RETRY_INTERVAL_MS, MAX_RETRIES, atomicWriteTails;
|
|
3734
|
-
var init_file_lock = __esm({
|
|
3735
|
-
"src/store/file-lock.ts"() {
|
|
3736
|
-
"use strict";
|
|
3737
|
-
init_esm_shims();
|
|
3738
|
-
LOCK_STALE_MS = 1e4;
|
|
3739
|
-
RETRY_INTERVAL_MS = 50;
|
|
3740
|
-
MAX_RETRIES = 60;
|
|
3741
|
-
atomicWriteTails = /* @__PURE__ */ new Map();
|
|
3742
|
-
}
|
|
3743
|
-
});
|
|
3744
|
-
|
|
3745
3776
|
// src/store/mini-skill-store.ts
|
|
3746
3777
|
import path7 from "path";
|
|
3747
3778
|
import fs4 from "fs";
|
|
@@ -143484,7 +143515,20 @@ function normalizeMiniMaxVideoRequest(input) {
|
|
|
143484
143515
|
if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
|
|
143485
143516
|
throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
|
|
143486
143517
|
}
|
|
143487
|
-
|
|
143518
|
+
let firstFrameImageUrl;
|
|
143519
|
+
if (input.firstFrameImageUrl !== void 0) {
|
|
143520
|
+
firstFrameImageUrl = input.firstFrameImageUrl.trim();
|
|
143521
|
+
let parsed;
|
|
143522
|
+
try {
|
|
143523
|
+
parsed = new URL(firstFrameImageUrl);
|
|
143524
|
+
} catch {
|
|
143525
|
+
throw new Error("MiniMax first-frame image must be a public HTTPS URL");
|
|
143526
|
+
}
|
|
143527
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
|
|
143528
|
+
throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
|
|
143529
|
+
}
|
|
143530
|
+
}
|
|
143531
|
+
return { region, model, prompt, resolution, duration, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
|
|
143488
143532
|
}
|
|
143489
143533
|
function videoRequestConfig(input) {
|
|
143490
143534
|
const normalized = normalizeMiniMaxVideoRequest(input);
|
|
@@ -143561,7 +143605,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
|
|
|
143561
143605
|
headers: videoHeaders(config.apiKey),
|
|
143562
143606
|
body: JSON.stringify({
|
|
143563
143607
|
model: config.model,
|
|
143564
|
-
content: [
|
|
143608
|
+
content: [
|
|
143609
|
+
{ type: "text", text: config.prompt },
|
|
143610
|
+
...config.firstFrameImageUrl ? [{
|
|
143611
|
+
type: "image_url",
|
|
143612
|
+
image_url: { url: config.firstFrameImageUrl },
|
|
143613
|
+
role: "first_frame"
|
|
143614
|
+
}] : []
|
|
143615
|
+
],
|
|
143565
143616
|
resolution: config.resolution,
|
|
143566
143617
|
duration: config.duration,
|
|
143567
143618
|
ratio: config.ratio
|
|
@@ -143722,10 +143773,19 @@ function parseStoredRequest(value) {
|
|
|
143722
143773
|
const duration = value.duration;
|
|
143723
143774
|
const ratio = value.ratio;
|
|
143724
143775
|
const maxBytes = value.maxBytes;
|
|
143725
|
-
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) {
|
|
143776
|
+
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) {
|
|
143726
143777
|
throw new Error("Media video job has an invalid request payload");
|
|
143727
143778
|
}
|
|
143728
|
-
|
|
143779
|
+
const normalized = normalizeMiniMaxVideoRequest({
|
|
143780
|
+
region,
|
|
143781
|
+
model,
|
|
143782
|
+
prompt,
|
|
143783
|
+
resolution,
|
|
143784
|
+
duration,
|
|
143785
|
+
ratio,
|
|
143786
|
+
firstFrameImageUrl: value.firstFrameImageUrl
|
|
143787
|
+
});
|
|
143788
|
+
return { provider: provider2, ...normalized, maxBytes };
|
|
143729
143789
|
}
|
|
143730
143790
|
function mediaJobIdFromMaintenance(job) {
|
|
143731
143791
|
const mediaJobId = job.payload.mediaJobId;
|
|
@@ -143737,6 +143797,7 @@ function mediaJobIdFromMaintenance(job) {
|
|
|
143737
143797
|
function toVideoRequest(request) {
|
|
143738
143798
|
return {
|
|
143739
143799
|
prompt: request.prompt,
|
|
143800
|
+
firstFrameImageUrl: request.firstFrameImageUrl,
|
|
143740
143801
|
model: request.model,
|
|
143741
143802
|
region: request.region,
|
|
143742
143803
|
resolution: request.resolution,
|
|
@@ -143828,6 +143889,7 @@ function queueMiniMaxVideoGeneration(input) {
|
|
|
143828
143889
|
region: normalized.region,
|
|
143829
143890
|
model: normalized.model,
|
|
143830
143891
|
prompt: normalized.prompt,
|
|
143892
|
+
...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
|
|
143831
143893
|
resolution: normalized.resolution,
|
|
143832
143894
|
duration: normalized.duration,
|
|
143833
143895
|
ratio: normalized.ratio,
|
|
@@ -805,18 +805,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
|
|
|
805
805
|
`;
|
|
806
806
|
CREATE_GRAPH_ENTITIES_TABLE = `
|
|
807
807
|
CREATE TABLE IF NOT EXISTS graph_entities (
|
|
808
|
-
|
|
808
|
+
projectId TEXT NOT NULL,
|
|
809
|
+
name TEXT NOT NULL,
|
|
809
810
|
entityType TEXT NOT NULL DEFAULT '',
|
|
810
|
-
observations TEXT NOT NULL DEFAULT '[]'
|
|
811
|
+
observations TEXT NOT NULL DEFAULT '[]',
|
|
812
|
+
PRIMARY KEY (projectId, name)
|
|
811
813
|
);
|
|
812
814
|
`;
|
|
813
815
|
CREATE_GRAPH_RELATIONS_TABLE = `
|
|
814
816
|
CREATE TABLE IF NOT EXISTS graph_relations (
|
|
815
817
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
818
|
+
projectId TEXT NOT NULL,
|
|
816
819
|
from_entity TEXT NOT NULL,
|
|
817
820
|
to_entity TEXT NOT NULL,
|
|
818
821
|
relationType TEXT NOT NULL DEFAULT '',
|
|
819
|
-
UNIQUE(from_entity, to_entity, relationType)
|
|
822
|
+
UNIQUE(projectId, from_entity, to_entity, relationType)
|
|
820
823
|
);
|
|
821
824
|
`;
|
|
822
825
|
CREATE_CODE_FILES_TABLE = `
|
|
@@ -2697,6 +2700,21 @@ var init_sqlite_reliability = __esm({
|
|
|
2697
2700
|
import { promises as fs2 } from "fs";
|
|
2698
2701
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2699
2702
|
import path4 from "path";
|
|
2703
|
+
function canReclaimStaleLock(raw) {
|
|
2704
|
+
try {
|
|
2705
|
+
const parsed = JSON.parse(raw);
|
|
2706
|
+
const pid = Number(parsed.pid);
|
|
2707
|
+
if (!Number.isSafeInteger(pid) || pid < 1) return true;
|
|
2708
|
+
try {
|
|
2709
|
+
process.kill(pid, 0);
|
|
2710
|
+
return false;
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
return error.code !== "EPERM";
|
|
2713
|
+
}
|
|
2714
|
+
} catch {
|
|
2715
|
+
return true;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2700
2718
|
async function acquireLock(lockPath) {
|
|
2701
2719
|
for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
|
|
2702
2720
|
try {
|
|
@@ -2710,9 +2728,12 @@ async function acquireLock(lockPath) {
|
|
|
2710
2728
|
try {
|
|
2711
2729
|
const stat5 = await fs2.stat(lockPath);
|
|
2712
2730
|
if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
|
|
2713
|
-
await fs2.
|
|
2714
|
-
|
|
2715
|
-
|
|
2731
|
+
const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
2732
|
+
if (canReclaimStaleLock(raw2)) {
|
|
2733
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
2734
|
+
});
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2716
2737
|
}
|
|
2717
2738
|
} catch {
|
|
2718
2739
|
continue;
|
|
@@ -2723,6 +2744,10 @@ async function acquireLock(lockPath) {
|
|
|
2723
2744
|
}
|
|
2724
2745
|
}
|
|
2725
2746
|
}
|
|
2747
|
+
const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
|
|
2748
|
+
if (!canReclaimStaleLock(raw)) {
|
|
2749
|
+
throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
|
|
2750
|
+
}
|
|
2726
2751
|
await fs2.unlink(lockPath).catch(() => {
|
|
2727
2752
|
});
|
|
2728
2753
|
try {
|
|
@@ -6653,6 +6678,7 @@ var init_sqlite_store = __esm({
|
|
|
6653
6678
|
init_sqlite_db();
|
|
6654
6679
|
init_sqlite_reliability();
|
|
6655
6680
|
init_sqlite_fts();
|
|
6681
|
+
init_file_lock();
|
|
6656
6682
|
SqliteBackend = class {
|
|
6657
6683
|
db = null;
|
|
6658
6684
|
dataDir = "";
|
|
@@ -6816,38 +6842,43 @@ var init_sqlite_store = __esm({
|
|
|
6816
6842
|
}
|
|
6817
6843
|
// ── Migration ────────────────────────────────────────────────────
|
|
6818
6844
|
async migrateFromJsonIfNeeded() {
|
|
6819
|
-
const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
|
|
6820
|
-
if (count2.cnt > 0) return;
|
|
6821
6845
|
const jsonPath = path7.join(this.dataDir, "observations.json");
|
|
6822
6846
|
if (!fs4.existsSync(jsonPath)) return;
|
|
6823
|
-
|
|
6847
|
+
await withFileLock(this.dataDir, async () => {
|
|
6848
|
+
const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
|
|
6849
|
+
if (count2.cnt > 0) return;
|
|
6824
6850
|
const raw = fs4.readFileSync(jsonPath, "utf-8");
|
|
6825
|
-
const
|
|
6851
|
+
const parsed = JSON.parse(raw);
|
|
6852
|
+
if (!Array.isArray(parsed)) {
|
|
6853
|
+
throw new Error("[memorix] observations.json migration refused: expected a JSON array");
|
|
6854
|
+
}
|
|
6855
|
+
const observations2 = parsed;
|
|
6826
6856
|
if (observations2.length === 0) return;
|
|
6857
|
+
if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
|
|
6858
|
+
throw new Error("[memorix] observations.json migration refused: invalid observation record");
|
|
6859
|
+
}
|
|
6827
6860
|
console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
|
|
6828
6861
|
const insertMany = this.db.transaction((obsList) => {
|
|
6829
6862
|
for (const obs of obsList) {
|
|
6830
6863
|
this.stmtInsert.run(obsToRow(obs));
|
|
6831
6864
|
}
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
if (fs4.existsSync(counterPath)) {
|
|
6836
|
-
try {
|
|
6865
|
+
const counterPath = path7.join(this.dataDir, "counter.json");
|
|
6866
|
+
let counterNextId;
|
|
6867
|
+
if (fs4.existsSync(counterPath)) {
|
|
6837
6868
|
const counterData = JSON.parse(fs4.readFileSync(counterPath, "utf-8"));
|
|
6838
|
-
const
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6869
|
+
const candidate = counterData.nextId ?? counterData.next_id;
|
|
6870
|
+
if (candidate !== void 0 && (!Number.isSafeInteger(candidate) || Number(candidate) < 1)) {
|
|
6871
|
+
throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
|
|
6872
|
+
}
|
|
6873
|
+
counterNextId = candidate === void 0 ? void 0 : Number(candidate);
|
|
6842
6874
|
}
|
|
6843
|
-
|
|
6844
|
-
this.rawSaveIdCounter(Math.max(
|
|
6845
|
-
|
|
6846
|
-
|
|
6875
|
+
const maxId = Math.max(...obsList.map((obs) => obs.id));
|
|
6876
|
+
this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
|
|
6877
|
+
this.bumpGeneration();
|
|
6878
|
+
});
|
|
6879
|
+
insertMany(observations2);
|
|
6847
6880
|
console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
|
|
6848
|
-
}
|
|
6849
|
-
console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
|
|
6850
|
-
}
|
|
6881
|
+
});
|
|
6851
6882
|
}
|
|
6852
6883
|
// ── Public read ──────────────────────────────────────────────────
|
|
6853
6884
|
async loadAll() {
|
|
@@ -143409,7 +143440,20 @@ function normalizeMiniMaxVideoRequest(input) {
|
|
|
143409
143440
|
if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
|
|
143410
143441
|
throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
|
|
143411
143442
|
}
|
|
143412
|
-
|
|
143443
|
+
let firstFrameImageUrl;
|
|
143444
|
+
if (input.firstFrameImageUrl !== void 0) {
|
|
143445
|
+
firstFrameImageUrl = input.firstFrameImageUrl.trim();
|
|
143446
|
+
let parsed;
|
|
143447
|
+
try {
|
|
143448
|
+
parsed = new URL(firstFrameImageUrl);
|
|
143449
|
+
} catch {
|
|
143450
|
+
throw new Error("MiniMax first-frame image must be a public HTTPS URL");
|
|
143451
|
+
}
|
|
143452
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
|
|
143453
|
+
throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
|
|
143454
|
+
}
|
|
143455
|
+
}
|
|
143456
|
+
return { region, model, prompt, resolution, duration, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
|
|
143413
143457
|
}
|
|
143414
143458
|
function videoRequestConfig(input) {
|
|
143415
143459
|
const normalized = normalizeMiniMaxVideoRequest(input);
|
|
@@ -143486,7 +143530,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
|
|
|
143486
143530
|
headers: videoHeaders(config.apiKey),
|
|
143487
143531
|
body: JSON.stringify({
|
|
143488
143532
|
model: config.model,
|
|
143489
|
-
content: [
|
|
143533
|
+
content: [
|
|
143534
|
+
{ type: "text", text: config.prompt },
|
|
143535
|
+
...config.firstFrameImageUrl ? [{
|
|
143536
|
+
type: "image_url",
|
|
143537
|
+
image_url: { url: config.firstFrameImageUrl },
|
|
143538
|
+
role: "first_frame"
|
|
143539
|
+
}] : []
|
|
143540
|
+
],
|
|
143490
143541
|
resolution: config.resolution,
|
|
143491
143542
|
duration: config.duration,
|
|
143492
143543
|
ratio: config.ratio
|
|
@@ -143647,10 +143698,19 @@ function parseStoredRequest(value) {
|
|
|
143647
143698
|
const duration = value.duration;
|
|
143648
143699
|
const ratio = value.ratio;
|
|
143649
143700
|
const maxBytes = value.maxBytes;
|
|
143650
|
-
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) {
|
|
143701
|
+
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) {
|
|
143651
143702
|
throw new Error("Media video job has an invalid request payload");
|
|
143652
143703
|
}
|
|
143653
|
-
|
|
143704
|
+
const normalized = normalizeMiniMaxVideoRequest({
|
|
143705
|
+
region,
|
|
143706
|
+
model,
|
|
143707
|
+
prompt,
|
|
143708
|
+
resolution,
|
|
143709
|
+
duration,
|
|
143710
|
+
ratio,
|
|
143711
|
+
firstFrameImageUrl: value.firstFrameImageUrl
|
|
143712
|
+
});
|
|
143713
|
+
return { provider: provider2, ...normalized, maxBytes };
|
|
143654
143714
|
}
|
|
143655
143715
|
function mediaJobIdFromMaintenance(job) {
|
|
143656
143716
|
const mediaJobId = job.payload.mediaJobId;
|
|
@@ -143662,6 +143722,7 @@ function mediaJobIdFromMaintenance(job) {
|
|
|
143662
143722
|
function toVideoRequest(request) {
|
|
143663
143723
|
return {
|
|
143664
143724
|
prompt: request.prompt,
|
|
143725
|
+
firstFrameImageUrl: request.firstFrameImageUrl,
|
|
143665
143726
|
model: request.model,
|
|
143666
143727
|
region: request.region,
|
|
143667
143728
|
resolution: request.resolution,
|
|
@@ -143753,6 +143814,7 @@ function queueMiniMaxVideoGeneration(input) {
|
|
|
143753
143814
|
region: normalized.region,
|
|
143754
143815
|
model: normalized.model,
|
|
143755
143816
|
prompt: normalized.prompt,
|
|
143817
|
+
...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
|
|
143756
143818
|
resolution: normalized.resolution,
|
|
143757
143819
|
duration: normalized.duration,
|
|
143758
143820
|
ratio: normalized.ratio,
|