memorix 1.3.3 → 1.4.0
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 +23 -0
- package/dist/cli/index.js +1162 -1098
- package/dist/index.d.ts +513 -0
- package/dist/index.js +14751 -14044
- package/dist/maintenance-runner.js +105 -52
- package/dist/media-video-runner.js +105 -52
- package/dist/memcode-runtime/CHANGELOG.md +23 -0
- package/dist/sdk.d.ts +5 -513
- package/dist/sdk.js +186 -75
- package/dist/vector-backfill-runner.js +105 -52
- package/docs/1.3.3-MULTIMODAL-MEDIA-AND-MCP-COMPAT.md +13 -13
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -3
- package/package.json +1 -1
- 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/server.json +2 -2
- package/src/cli/commands/media.ts +10 -2
- package/src/cli/commands/serve-http.ts +7 -38
- package/src/cli/commands/serve-shared.ts +18 -46
- package/src/cli/commands/serve.ts +3 -41
- package/src/cli/commands/setup.ts +24 -21
- package/src/cli/mcp-root-path.ts +36 -0
- package/src/index.ts +37 -55
- package/src/media/asset-store.ts +74 -6
- package/src/media/attachment.ts +12 -9
- package/src/media/media-store.ts +38 -4
- package/src/media/video-jobs.ts +39 -14
- package/src/memory/observations.ts +21 -2
- package/src/orchestrate/adapters/spawn-helper.ts +2 -0
- package/src/server/tool-profile.ts +7 -4
- package/src/server.ts +6 -2
package/dist/sdk.js
CHANGED
|
@@ -1186,8 +1186,8 @@ async function acquireLock(lockPath) {
|
|
|
1186
1186
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1187
1187
|
if (code === "EEXIST" || code === "EPERM") {
|
|
1188
1188
|
try {
|
|
1189
|
-
const
|
|
1190
|
-
if (Date.now() -
|
|
1189
|
+
const stat4 = await fs2.stat(lockPath);
|
|
1190
|
+
if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
|
|
1191
1191
|
await fs2.unlink(lockPath).catch(() => {
|
|
1192
1192
|
});
|
|
1193
1193
|
continue;
|
|
@@ -9347,6 +9347,10 @@ var init_media_store = __esm({
|
|
|
9347
9347
|
`).run(projectId, assetId).changes ?? 0);
|
|
9348
9348
|
this.db.prepare("DELETE FROM media_derivations WHERE project_id = ? AND asset_id = ?").run(projectId, assetId);
|
|
9349
9349
|
this.db.prepare("DELETE FROM media_embeddings WHERE project_id = ? AND asset_id = ?").run(projectId, assetId);
|
|
9350
|
+
this.db.prepare(`
|
|
9351
|
+
UPDATE media_jobs SET asset_id = NULL, updated_at = ?
|
|
9352
|
+
WHERE project_id = ? AND asset_id = ?
|
|
9353
|
+
`).run(Date.now(), projectId, assetId);
|
|
9350
9354
|
const result = this.db.prepare(`
|
|
9351
9355
|
UPDATE media_assets SET deleted_at = ?, updated_at = ?
|
|
9352
9356
|
WHERE project_id = ? AND id = ? AND deleted_at IS NULL
|
|
@@ -9521,16 +9525,28 @@ var init_media_store = __esm({
|
|
|
9521
9525
|
return row ? rowToJob2(row) : void 0;
|
|
9522
9526
|
}
|
|
9523
9527
|
updateJob(projectId, id, input) {
|
|
9528
|
+
const updated = this.updateJobInternal(projectId, id, input, false);
|
|
9529
|
+
if (!updated) throw new Error(`Media job not found: ${id}`);
|
|
9530
|
+
return updated;
|
|
9531
|
+
}
|
|
9532
|
+
/**
|
|
9533
|
+
* Atomically preserve a user cancellation against a worker that was already
|
|
9534
|
+
* downloading or attaching media when the cancellation arrived.
|
|
9535
|
+
*/
|
|
9536
|
+
updateJobIfNotCancelled(projectId, id, input) {
|
|
9537
|
+
return this.updateJobInternal(projectId, id, input, true);
|
|
9538
|
+
}
|
|
9539
|
+
updateJobInternal(projectId, id, input, rejectCancelled) {
|
|
9524
9540
|
const existing = this.getJob(projectId, id);
|
|
9525
|
-
if (!existing)
|
|
9541
|
+
if (!existing) return void 0;
|
|
9526
9542
|
const now3 = Date.now();
|
|
9527
9543
|
const attempts = existing.attempts + (input.incrementAttempts ? 1 : 0);
|
|
9528
9544
|
const completedAt = input.status === "completed" ? now3 : null;
|
|
9529
|
-
this.db.prepare(`
|
|
9545
|
+
const result = this.db.prepare(`
|
|
9530
9546
|
UPDATE media_jobs
|
|
9531
9547
|
SET status = ?, provider_task_id = COALESCE(?, provider_task_id), asset_id = COALESCE(?, asset_id),
|
|
9532
9548
|
last_error = ?, attempts = ?, updated_at = ?, completed_at = ?
|
|
9533
|
-
WHERE project_id = ? AND id = ?
|
|
9549
|
+
WHERE project_id = ? AND id = ? ${rejectCancelled ? "AND status <> 'cancelled'" : ""}
|
|
9534
9550
|
`).run(
|
|
9535
9551
|
input.status,
|
|
9536
9552
|
input.providerTaskId ?? null,
|
|
@@ -9542,6 +9558,7 @@ var init_media_store = __esm({
|
|
|
9542
9558
|
projectId,
|
|
9543
9559
|
id
|
|
9544
9560
|
);
|
|
9561
|
+
if (Number(result.changes ?? 0) !== 1) return void 0;
|
|
9545
9562
|
return this.getJob(projectId, id);
|
|
9546
9563
|
}
|
|
9547
9564
|
cancelJob(projectId, id) {
|
|
@@ -9591,6 +9608,7 @@ async function attachMediaAssetToObservation(input) {
|
|
|
9591
9608
|
`MIME type: ${asset.mimeType}`,
|
|
9592
9609
|
...input.facts ?? []
|
|
9593
9610
|
];
|
|
9611
|
+
const store = new MediaStore(input.dataDir);
|
|
9594
9612
|
const result = await storeObservation({
|
|
9595
9613
|
entityName: input.entityName?.trim() || sourceLabel4.replace(/\.[^.]+$/, "") || "media-asset",
|
|
9596
9614
|
type: "discovery",
|
|
@@ -9603,13 +9621,15 @@ async function attachMediaAssetToObservation(input) {
|
|
|
9603
9621
|
sourceDetail: "explicit",
|
|
9604
9622
|
...input.visibility ? { visibility: input.visibility } : {},
|
|
9605
9623
|
...input.createdByAgentId ? { createdByAgentId: input.createdByAgentId } : {},
|
|
9606
|
-
...input.visibilityReader ? { visibilityReader: input.visibilityReader } : {}
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9624
|
+
...input.visibilityReader ? { visibilityReader: input.visibilityReader } : {},
|
|
9625
|
+
onPersisted: (observation) => {
|
|
9626
|
+
store.linkAsset({
|
|
9627
|
+
projectId: input.projectId,
|
|
9628
|
+
assetId: asset.id,
|
|
9629
|
+
observationId: observation.id,
|
|
9630
|
+
role: input.role ?? "attachment"
|
|
9631
|
+
});
|
|
9632
|
+
}
|
|
9613
9633
|
});
|
|
9614
9634
|
return result.observation;
|
|
9615
9635
|
}
|
|
@@ -9637,6 +9657,7 @@ var init_types2 = __esm({
|
|
|
9637
9657
|
var asset_store_exports = {};
|
|
9638
9658
|
__export(asset_store_exports, {
|
|
9639
9659
|
cleanupMediaQuota: () => cleanupMediaQuota,
|
|
9660
|
+
cleanupMediaTrash: () => cleanupMediaTrash,
|
|
9640
9661
|
getMediaAssetPath: () => getMediaAssetPath,
|
|
9641
9662
|
importMediaBuffer: () => importMediaBuffer,
|
|
9642
9663
|
importMediaFile: () => importMediaFile,
|
|
@@ -9644,7 +9665,7 @@ __export(asset_store_exports, {
|
|
|
9644
9665
|
removeMediaAsset: () => removeMediaAsset
|
|
9645
9666
|
});
|
|
9646
9667
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
9647
|
-
import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, rename, rm, writeFile as writeFile3 } from "fs/promises";
|
|
9668
|
+
import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
|
|
9648
9669
|
import path13 from "path";
|
|
9649
9670
|
function mediaRoot(dataDir) {
|
|
9650
9671
|
return path13.resolve(dataDir, "media");
|
|
@@ -9790,11 +9811,11 @@ async function importKnownMedia(input) {
|
|
|
9790
9811
|
async function importMediaFile(input) {
|
|
9791
9812
|
const maxBytes = input.maxBytes ?? DEFAULT_MAX_MEDIA_BYTES;
|
|
9792
9813
|
const source = path13.resolve(input.filePath);
|
|
9793
|
-
const
|
|
9794
|
-
if (
|
|
9795
|
-
if (!
|
|
9796
|
-
if (
|
|
9797
|
-
if (
|
|
9814
|
+
const stat4 = await lstat(source);
|
|
9815
|
+
if (stat4.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
|
|
9816
|
+
if (!stat4.isFile()) throw new Error("Media import accepts regular files only");
|
|
9817
|
+
if (stat4.size <= 0) throw new Error("Media import rejects empty files");
|
|
9818
|
+
if (stat4.size > maxBytes) throw new Error(`Media file is too large (${stat4.size} bytes; limit ${maxBytes})`);
|
|
9798
9819
|
const header = await readHeader(source);
|
|
9799
9820
|
const detected = detectMedia(header);
|
|
9800
9821
|
const sha256 = await sha256File(source);
|
|
@@ -9802,7 +9823,7 @@ async function importMediaFile(input) {
|
|
|
9802
9823
|
dataDir: input.dataDir,
|
|
9803
9824
|
projectId: input.projectId,
|
|
9804
9825
|
sha256,
|
|
9805
|
-
bytes:
|
|
9826
|
+
bytes: stat4.size,
|
|
9806
9827
|
detected,
|
|
9807
9828
|
persist: (destination) => persistFile(source, destination),
|
|
9808
9829
|
sourceKind: input.sourceKind ?? "import",
|
|
@@ -9865,16 +9886,58 @@ async function removeMediaAsset(input) {
|
|
|
9865
9886
|
}
|
|
9866
9887
|
await rm(stagedPath, { force: true }).catch(() => {
|
|
9867
9888
|
});
|
|
9868
|
-
return { asset, detachedLinks };
|
|
9889
|
+
return { asset, detachedLinks, pendingDelete: await destinationExists(stagedPath) };
|
|
9890
|
+
}
|
|
9891
|
+
async function cleanupMediaTrash(dataDir) {
|
|
9892
|
+
const root = mediaRoot(dataDir);
|
|
9893
|
+
const trashDir = validateUnderRoot(root, path13.join(root, ".trash"));
|
|
9894
|
+
let entries;
|
|
9895
|
+
try {
|
|
9896
|
+
entries = await readdir(trashDir);
|
|
9897
|
+
} catch (error) {
|
|
9898
|
+
if (error?.code === "ENOENT") return { reclaimedBytes: 0, pendingBytes: 0, pendingFiles: 0 };
|
|
9899
|
+
throw error;
|
|
9900
|
+
}
|
|
9901
|
+
let reclaimedBytes = 0;
|
|
9902
|
+
let pendingBytes = 0;
|
|
9903
|
+
let pendingFiles = 0;
|
|
9904
|
+
for (const name of entries) {
|
|
9905
|
+
if (!name.endsWith(".pending-delete")) continue;
|
|
9906
|
+
const candidate = validateUnderRoot(trashDir, path13.join(trashDir, name));
|
|
9907
|
+
try {
|
|
9908
|
+
const metadata = await lstat(candidate);
|
|
9909
|
+
if (!metadata.isFile()) continue;
|
|
9910
|
+
await rm(candidate, { force: true });
|
|
9911
|
+
reclaimedBytes += metadata.size;
|
|
9912
|
+
} catch (error) {
|
|
9913
|
+
if (error?.code === "ENOENT") continue;
|
|
9914
|
+
try {
|
|
9915
|
+
pendingBytes += (await stat(candidate)).size;
|
|
9916
|
+
} catch {
|
|
9917
|
+
}
|
|
9918
|
+
pendingFiles += 1;
|
|
9919
|
+
}
|
|
9920
|
+
}
|
|
9921
|
+
return { reclaimedBytes, pendingBytes, pendingFiles };
|
|
9869
9922
|
}
|
|
9870
9923
|
async function cleanupMediaQuota(input) {
|
|
9871
9924
|
const store = new MediaStore(input.dataDir);
|
|
9872
9925
|
const maxBytes = input.maxBytes ?? DEFAULT_MEDIA_QUOTA_BYTES;
|
|
9873
9926
|
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new Error("maxBytes must be a non-negative integer");
|
|
9927
|
+
const initialTrash = await cleanupMediaTrash(input.dataDir);
|
|
9874
9928
|
const beforeBytes = store.activeByteSize(input.projectId);
|
|
9875
9929
|
let currentBytes = beforeBytes;
|
|
9876
9930
|
const removed = [];
|
|
9877
|
-
if (currentBytes <= maxBytes)
|
|
9931
|
+
if (currentBytes <= maxBytes) {
|
|
9932
|
+
return {
|
|
9933
|
+
beforeBytes,
|
|
9934
|
+
afterBytes: currentBytes,
|
|
9935
|
+
removed,
|
|
9936
|
+
reclaimedTrashBytes: initialTrash.reclaimedBytes,
|
|
9937
|
+
pendingTrashBytes: initialTrash.pendingBytes,
|
|
9938
|
+
pendingTrashFiles: initialTrash.pendingFiles
|
|
9939
|
+
};
|
|
9940
|
+
}
|
|
9878
9941
|
const candidates = store.listUnlinkedAssets(input.projectId);
|
|
9879
9942
|
for (const asset of candidates) {
|
|
9880
9943
|
if (currentBytes <= maxBytes) break;
|
|
@@ -9887,7 +9950,15 @@ async function cleanupMediaQuota(input) {
|
|
|
9887
9950
|
currentBytes -= asset.byteSize;
|
|
9888
9951
|
removed.push(asset);
|
|
9889
9952
|
}
|
|
9890
|
-
|
|
9953
|
+
const finalTrash = await cleanupMediaTrash(input.dataDir);
|
|
9954
|
+
return {
|
|
9955
|
+
beforeBytes,
|
|
9956
|
+
afterBytes: Math.max(0, currentBytes),
|
|
9957
|
+
removed,
|
|
9958
|
+
reclaimedTrashBytes: initialTrash.reclaimedBytes + finalTrash.reclaimedBytes,
|
|
9959
|
+
pendingTrashBytes: finalTrash.pendingBytes,
|
|
9960
|
+
pendingTrashFiles: finalTrash.pendingFiles
|
|
9961
|
+
};
|
|
9891
9962
|
}
|
|
9892
9963
|
var init_asset_store = __esm({
|
|
9893
9964
|
"src/media/asset-store.ts"() {
|
|
@@ -60894,7 +60965,7 @@ var require_node_domexception = __commonJS({
|
|
|
60894
60965
|
// node_modules/fetch-blob/from.js
|
|
60895
60966
|
import { statSync as statSync2, createReadStream, promises as fs10 } from "fs";
|
|
60896
60967
|
import { basename } from "path";
|
|
60897
|
-
var import_node_domexception,
|
|
60968
|
+
var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
|
|
60898
60969
|
var init_from = __esm({
|
|
60899
60970
|
"node_modules/fetch-blob/from.js"() {
|
|
60900
60971
|
"use strict";
|
|
@@ -60902,23 +60973,23 @@ var init_from = __esm({
|
|
|
60902
60973
|
import_node_domexception = __toESM(require_node_domexception(), 1);
|
|
60903
60974
|
init_file();
|
|
60904
60975
|
init_fetch_blob();
|
|
60905
|
-
({ stat } = fs10);
|
|
60976
|
+
({ stat: stat2 } = fs10);
|
|
60906
60977
|
blobFromSync = (path33, type) => fromBlob(statSync2(path33), path33, type);
|
|
60907
|
-
blobFrom = (path33, type) =>
|
|
60908
|
-
fileFrom = (path33, type) =>
|
|
60978
|
+
blobFrom = (path33, type) => stat2(path33).then((stat4) => fromBlob(stat4, path33, type));
|
|
60979
|
+
fileFrom = (path33, type) => stat2(path33).then((stat4) => fromFile(stat4, path33, type));
|
|
60909
60980
|
fileFromSync = (path33, type) => fromFile(statSync2(path33), path33, type);
|
|
60910
|
-
fromBlob = (
|
|
60981
|
+
fromBlob = (stat4, path33, type = "") => new fetch_blob_default([new BlobDataItem({
|
|
60911
60982
|
path: path33,
|
|
60912
|
-
size:
|
|
60913
|
-
lastModified:
|
|
60983
|
+
size: stat4.size,
|
|
60984
|
+
lastModified: stat4.mtimeMs,
|
|
60914
60985
|
start: 0
|
|
60915
60986
|
})], { type });
|
|
60916
|
-
fromFile = (
|
|
60987
|
+
fromFile = (stat4, path33, type = "") => new file_default([new BlobDataItem({
|
|
60917
60988
|
path: path33,
|
|
60918
|
-
size:
|
|
60919
|
-
lastModified:
|
|
60989
|
+
size: stat4.size,
|
|
60990
|
+
lastModified: stat4.mtimeMs,
|
|
60920
60991
|
start: 0
|
|
60921
|
-
})], basename(path33), { type, lastModified:
|
|
60992
|
+
})], basename(path33), { type, lastModified: stat4.mtimeMs });
|
|
60922
60993
|
BlobDataItem = class _BlobDataItem {
|
|
60923
60994
|
#path;
|
|
60924
60995
|
#start;
|
|
@@ -60941,7 +61012,7 @@ var init_from = __esm({
|
|
|
60941
61012
|
});
|
|
60942
61013
|
}
|
|
60943
61014
|
async *stream() {
|
|
60944
|
-
const { mtimeMs } = await
|
|
61015
|
+
const { mtimeMs } = await stat2(this.#path);
|
|
60945
61016
|
if (mtimeMs > this.lastModified) {
|
|
60946
61017
|
throw new import_node_domexception.default("The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.", "NotReadableError");
|
|
60947
61018
|
}
|
|
@@ -143218,10 +143289,17 @@ function sourceLabel2(request, providerTaskId) {
|
|
|
143218
143289
|
}
|
|
143219
143290
|
async function finishWithAsset(input) {
|
|
143220
143291
|
const { dataDir, projectId, store, mediaJob, request, providerTask, dependencies } = input;
|
|
143221
|
-
let
|
|
143292
|
+
let activeJob = store.getJob(projectId, mediaJob.id);
|
|
143293
|
+
if (!activeJob || terminal(activeJob)) return { action: "complete" };
|
|
143294
|
+
let asset = activeJob.assetId ? store.getAsset(projectId, activeJob.assetId) : void 0;
|
|
143222
143295
|
if (!asset) {
|
|
143223
143296
|
if (!providerTask.downloadUrl) throw new Error("Succeeded MiniMax video task did not include a download URL");
|
|
143224
|
-
store.
|
|
143297
|
+
const downloading = store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
143298
|
+
status: "downloading",
|
|
143299
|
+
providerTaskId: providerTask.taskId
|
|
143300
|
+
});
|
|
143301
|
+
if (!downloading) return { action: "complete" };
|
|
143302
|
+
activeJob = downloading;
|
|
143225
143303
|
const bytes = await (dependencies.download ?? ((downloadInput) => downloadProviderMedia(downloadInput)))({
|
|
143226
143304
|
url: providerTask.downloadUrl,
|
|
143227
143305
|
maxBytes: request.maxBytes
|
|
@@ -143238,21 +143316,30 @@ async function finishWithAsset(input) {
|
|
|
143238
143316
|
maxBytes: request.maxBytes
|
|
143239
143317
|
});
|
|
143240
143318
|
asset = imported.asset;
|
|
143319
|
+
const persistedAsset = store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
143320
|
+
status: "downloading",
|
|
143321
|
+
providerTaskId: providerTask.taskId,
|
|
143322
|
+
assetId: asset.id
|
|
143323
|
+
});
|
|
143324
|
+
if (!persistedAsset) return { action: "complete" };
|
|
143325
|
+
activeJob = persistedAsset;
|
|
143241
143326
|
}
|
|
143242
|
-
if (
|
|
143327
|
+
if (activeJob.attachOnComplete) {
|
|
143328
|
+
activeJob = store.getJob(projectId, activeJob.id);
|
|
143329
|
+
if (!activeJob || terminal(activeJob)) return { action: "complete" };
|
|
143243
143330
|
const hasAttachment = store.listLinks(projectId, asset.id).some((link) => link.role === "attachment" && link.observationId !== void 0);
|
|
143244
143331
|
if (!hasAttachment) {
|
|
143245
143332
|
await (dependencies.attach ?? attachMediaAssetToObservation)({
|
|
143246
143333
|
dataDir,
|
|
143247
143334
|
projectId,
|
|
143248
143335
|
asset,
|
|
143249
|
-
title:
|
|
143336
|
+
title: activeJob.observationTitle ?? `MiniMax video: ${asset.sourceLabel ?? asset.id}`,
|
|
143250
143337
|
narrative: `Generated with ${request.provider}/${request.model}. Prompt: ${request.prompt}`,
|
|
143251
143338
|
concepts: ["generated-video", "minimax", request.model]
|
|
143252
143339
|
});
|
|
143253
143340
|
}
|
|
143254
143341
|
}
|
|
143255
|
-
store.
|
|
143342
|
+
store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
143256
143343
|
status: "completed",
|
|
143257
143344
|
providerTaskId: providerTask.taskId,
|
|
143258
143345
|
assetId: asset.id
|
|
@@ -143262,10 +143349,12 @@ async function finishWithAsset(input) {
|
|
|
143262
143349
|
function retryAfterProviderError(store, projectId, mediaJob, maintenanceJob, error) {
|
|
143263
143350
|
const message = safeError2(error);
|
|
143264
143351
|
if (maintenanceJob.attempts >= maintenanceJob.maxAttempts) {
|
|
143265
|
-
store.
|
|
143352
|
+
store.updateJobIfNotCancelled(projectId, mediaJob.id, { status: "failed", lastError: message, incrementAttempts: true });
|
|
143353
|
+
return { action: "complete" };
|
|
143354
|
+
}
|
|
143355
|
+
if (!store.updateJobIfNotCancelled(projectId, mediaJob.id, { status: "retry", lastError: message, incrementAttempts: true })) {
|
|
143266
143356
|
return { action: "complete" };
|
|
143267
143357
|
}
|
|
143268
|
-
store.updateJob(projectId, mediaJob.id, { status: "retry", lastError: message, incrementAttempts: true });
|
|
143269
143358
|
return { action: "reschedule", delayMs: MINI_MAX_VIDEO_POLL_MS, status: "retry", lastError: message };
|
|
143270
143359
|
}
|
|
143271
143360
|
function queueMiniMaxVideoGeneration(input) {
|
|
@@ -143306,22 +143395,25 @@ async function runMiniMaxVideoGenerationJob(maintenanceJob, context, dependencie
|
|
|
143306
143395
|
if (mediaJob.kind !== "minimax-video-generation") throw new Error(`Unsupported media job kind: ${mediaJob.kind}`);
|
|
143307
143396
|
const request = parseStoredRequest(mediaJob.request);
|
|
143308
143397
|
if (!mediaJob.providerTaskId) {
|
|
143309
|
-
store.
|
|
143398
|
+
if (!store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "submitting", incrementAttempts: true })) {
|
|
143399
|
+
return { action: "complete" };
|
|
143400
|
+
}
|
|
143310
143401
|
let submitted;
|
|
143311
143402
|
try {
|
|
143312
143403
|
submitted = await (dependencies.createTask ?? createMiniMaxVideoTask)(toVideoRequest(request));
|
|
143313
143404
|
} catch (error) {
|
|
143314
|
-
store.
|
|
143405
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", lastError: safeError2(error) });
|
|
143315
143406
|
return { action: "complete" };
|
|
143316
143407
|
}
|
|
143317
143408
|
if (submitted.status === "failed") {
|
|
143318
|
-
store.
|
|
143409
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", providerTaskId: submitted.taskId, lastError: submitted.error });
|
|
143319
143410
|
return { action: "complete" };
|
|
143320
143411
|
}
|
|
143321
|
-
const pending = store.
|
|
143412
|
+
const pending = store.updateJobIfNotCancelled(context.projectId, mediaJob.id, {
|
|
143322
143413
|
status: submitted.status === "succeeded" ? "downloading" : "provider-pending",
|
|
143323
143414
|
providerTaskId: submitted.taskId
|
|
143324
143415
|
});
|
|
143416
|
+
if (!pending) return { action: "complete" };
|
|
143325
143417
|
if (submitted.status === "succeeded") {
|
|
143326
143418
|
try {
|
|
143327
143419
|
return await finishWithAsset({
|
|
@@ -143349,11 +143441,13 @@ async function runMiniMaxVideoGenerationJob(maintenanceJob, context, dependencie
|
|
|
143349
143441
|
return retryAfterProviderError(store, context.projectId, mediaJob, maintenanceJob, error);
|
|
143350
143442
|
}
|
|
143351
143443
|
if (task.status === "pending") {
|
|
143352
|
-
store.
|
|
143444
|
+
if (!store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "provider-pending", providerTaskId: task.taskId })) {
|
|
143445
|
+
return { action: "complete" };
|
|
143446
|
+
}
|
|
143353
143447
|
return { action: "reschedule", delayMs: MINI_MAX_VIDEO_POLL_MS, resetAttempts: true, clearLastError: true };
|
|
143354
143448
|
}
|
|
143355
143449
|
if (task.status === "failed") {
|
|
143356
|
-
store.
|
|
143450
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", providerTaskId: task.taskId, lastError: task.error, incrementAttempts: true });
|
|
143357
143451
|
return { action: "complete" };
|
|
143358
143452
|
}
|
|
143359
143453
|
try {
|
|
@@ -144636,7 +144730,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
|
|
|
144636
144730
|
const rel = normalizeCodePath(relative(projectRoot, abs));
|
|
144637
144731
|
try {
|
|
144638
144732
|
const text = readFileSync6(abs, "utf-8");
|
|
144639
|
-
const
|
|
144733
|
+
const stat4 = fileStat ?? statSync3(abs);
|
|
144640
144734
|
const file = {
|
|
144641
144735
|
id: makeCodeFileId(projectId, rel),
|
|
144642
144736
|
projectId,
|
|
@@ -144645,8 +144739,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
|
|
|
144645
144739
|
contentHash: hashText(text),
|
|
144646
144740
|
// Preserve filesystem precision. Rounding here made same-size edits in
|
|
144647
144741
|
// a short Windows timestamp window indistinguishable to refresh.
|
|
144648
|
-
mtimeMs: Number(
|
|
144649
|
-
sizeBytes: Number(
|
|
144742
|
+
mtimeMs: Number(stat4.mtimeMs),
|
|
144743
|
+
sizeBytes: Number(stat4.size),
|
|
144650
144744
|
indexedAt
|
|
144651
144745
|
};
|
|
144652
144746
|
return {
|
|
@@ -144673,18 +144767,18 @@ async function indexProjectLite(options) {
|
|
|
144673
144767
|
let skippedOversizedFiles = 0;
|
|
144674
144768
|
let unreadableFiles = 0;
|
|
144675
144769
|
for (const abs of paths) {
|
|
144676
|
-
let
|
|
144770
|
+
let stat4;
|
|
144677
144771
|
try {
|
|
144678
|
-
|
|
144772
|
+
stat4 = statSync3(abs);
|
|
144679
144773
|
} catch {
|
|
144680
144774
|
unreadableFiles++;
|
|
144681
144775
|
continue;
|
|
144682
144776
|
}
|
|
144683
|
-
if (Number(
|
|
144777
|
+
if (Number(stat4.size) > maxFileBytes) {
|
|
144684
144778
|
skippedOversizedFiles++;
|
|
144685
144779
|
continue;
|
|
144686
144780
|
}
|
|
144687
|
-
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt,
|
|
144781
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
|
|
144688
144782
|
if (!attempt.delta) {
|
|
144689
144783
|
if (attempt.unreadable) unreadableFiles++;
|
|
144690
144784
|
continue;
|
|
@@ -144712,16 +144806,16 @@ async function refreshProjectLite(store, options) {
|
|
|
144712
144806
|
for (const abs of paths) {
|
|
144713
144807
|
const rel = normalizeCodePath(relative(options.projectRoot, abs));
|
|
144714
144808
|
seenPaths.add(rel);
|
|
144715
|
-
let
|
|
144809
|
+
let stat4;
|
|
144716
144810
|
try {
|
|
144717
|
-
|
|
144811
|
+
stat4 = statSync3(abs);
|
|
144718
144812
|
} catch {
|
|
144719
144813
|
unreadableFiles++;
|
|
144720
144814
|
continue;
|
|
144721
144815
|
}
|
|
144722
144816
|
const existing = existingByPath.get(rel);
|
|
144723
|
-
const mtimeMs = Number(
|
|
144724
|
-
const sizeBytes = Number(
|
|
144817
|
+
const mtimeMs = Number(stat4.mtimeMs);
|
|
144818
|
+
const sizeBytes = Number(stat4.size);
|
|
144725
144819
|
if (sizeBytes > maxFileBytes) {
|
|
144726
144820
|
skippedOversizedFiles++;
|
|
144727
144821
|
if (existing) oversizedExistingFileIds.add(existing.id);
|
|
@@ -144731,7 +144825,7 @@ async function refreshProjectLite(store, options) {
|
|
|
144731
144825
|
unchangedFiles++;
|
|
144732
144826
|
continue;
|
|
144733
144827
|
}
|
|
144734
|
-
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt,
|
|
144828
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
|
|
144735
144829
|
if (!attempt.delta) {
|
|
144736
144830
|
if (attempt.unreadable) unreadableFiles++;
|
|
144737
144831
|
continue;
|
|
@@ -148513,7 +148607,9 @@ async function storeObservation(input) {
|
|
|
148513
148607
|
if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
|
|
148514
148608
|
throw new Error("Cannot update a memory outside this session's write scope.");
|
|
148515
148609
|
}
|
|
148516
|
-
|
|
148610
|
+
const observation2 = await upsertObservation(existing, input, now3);
|
|
148611
|
+
await input.onPersisted?.(observation2);
|
|
148612
|
+
return { observation: observation2, upserted: true };
|
|
148517
148613
|
}
|
|
148518
148614
|
}
|
|
148519
148615
|
const contentForExtraction = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
@@ -148538,6 +148634,7 @@ async function storeObservation(input) {
|
|
|
148538
148634
|
let doc;
|
|
148539
148635
|
let upsertedInsideLock = false;
|
|
148540
148636
|
let reloadCacheAfterCommit = false;
|
|
148637
|
+
let persistedCallbackInvoked = false;
|
|
148541
148638
|
const assignAndPersist = async () => {
|
|
148542
148639
|
if (projectDir) {
|
|
148543
148640
|
const store = getObservationStore();
|
|
@@ -148593,6 +148690,8 @@ async function storeObservation(input) {
|
|
|
148593
148690
|
writeGeneration: transactionGeneration + 1
|
|
148594
148691
|
};
|
|
148595
148692
|
await tx.insert(observation);
|
|
148693
|
+
await input.onPersisted?.(observation);
|
|
148694
|
+
persistedCallbackInvoked = true;
|
|
148596
148695
|
await tx.saveIdCounter(id + 1);
|
|
148597
148696
|
});
|
|
148598
148697
|
observation.writeGeneration = store.getGeneration();
|
|
@@ -148642,6 +148741,8 @@ async function storeObservation(input) {
|
|
|
148642
148741
|
writeGeneration: 0
|
|
148643
148742
|
};
|
|
148644
148743
|
observations.push(observation);
|
|
148744
|
+
await input.onPersisted?.(observation);
|
|
148745
|
+
persistedCallbackInvoked = true;
|
|
148645
148746
|
}
|
|
148646
148747
|
doc = {
|
|
148647
148748
|
id: makeOramaObservationId(input.projectId, observation.id),
|
|
@@ -148673,8 +148774,11 @@ async function storeObservation(input) {
|
|
|
148673
148774
|
};
|
|
148674
148775
|
await assignAndPersist();
|
|
148675
148776
|
if (upsertedInsideLock) {
|
|
148676
|
-
|
|
148777
|
+
const updated = await upsertObservation(observation, input, now3);
|
|
148778
|
+
await input.onPersisted?.(updated);
|
|
148779
|
+
return { observation: updated, upserted: true };
|
|
148677
148780
|
}
|
|
148781
|
+
if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
|
|
148678
148782
|
await bindObservationCodeRefsBestEffort(observation);
|
|
148679
148783
|
queueObservationQualification(observation);
|
|
148680
148784
|
if (resolveObservationVisibility(observation) === "project") {
|
|
@@ -153826,9 +153930,9 @@ function existingLensCandidates(rootPath, lens) {
|
|
|
153826
153930
|
const absolute = path27.join(rootPath, candidate);
|
|
153827
153931
|
try {
|
|
153828
153932
|
if (!existsSync15(absolute)) continue;
|
|
153829
|
-
const
|
|
153830
|
-
if (
|
|
153831
|
-
if (
|
|
153933
|
+
const stat4 = statSync4(absolute);
|
|
153934
|
+
if (stat4.isFile()) out.push(candidate);
|
|
153935
|
+
if (stat4.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
|
|
153832
153936
|
} catch {
|
|
153833
153937
|
}
|
|
153834
153938
|
}
|
|
@@ -159965,11 +160069,11 @@ import path32 from "path";
|
|
|
159965
160069
|
function resolveGitDir(projectRoot) {
|
|
159966
160070
|
const dotGit = path32.join(projectRoot, ".git");
|
|
159967
160071
|
if (!existsSync18(dotGit)) return null;
|
|
159968
|
-
const
|
|
159969
|
-
if (
|
|
160072
|
+
const stat4 = statSync5(dotGit);
|
|
160073
|
+
if (stat4.isDirectory()) {
|
|
159970
160074
|
return dotGit;
|
|
159971
160075
|
}
|
|
159972
|
-
if (
|
|
160076
|
+
if (stat4.isFile()) {
|
|
159973
160077
|
try {
|
|
159974
160078
|
const content = readFileSync13(dotGit, "utf-8").trim();
|
|
159975
160079
|
const match2 = content.match(/^gitdir:\s*(.+)$/);
|
|
@@ -162007,8 +162111,8 @@ var RulesSyncer = class {
|
|
|
162007
162111
|
const dir = path23.dirname(pattern);
|
|
162008
162112
|
const fileGlob = path23.basename(pattern);
|
|
162009
162113
|
try {
|
|
162010
|
-
const
|
|
162011
|
-
if (!
|
|
162114
|
+
const stat4 = await fs16.stat(dir);
|
|
162115
|
+
if (!stat4.isDirectory()) {
|
|
162012
162116
|
try {
|
|
162013
162117
|
await fs16.access(pattern);
|
|
162014
162118
|
return [pattern];
|
|
@@ -163473,18 +163577,21 @@ var TOOL_PROFILES = Object.freeze({
|
|
|
163473
163577
|
memorix_context_pack: ["micro", "lite", "team", "full"],
|
|
163474
163578
|
memorix_codegraph_status: ["micro", "lite", "team", "full"],
|
|
163475
163579
|
memorix_resolve: ["micro", "lite", "team", "full"],
|
|
163580
|
+
// A fail-closed stdio server needs an explicit recovery path when a client
|
|
163581
|
+
// did not launch it from the workspace. The unified media tool keeps
|
|
163582
|
+
// multimodal support to one additional schema rather than enabling lite.
|
|
163583
|
+
memorix_session_start: ["micro", "lite", "team", "full"],
|
|
163584
|
+
memorix_media: ["micro", "lite", "team", "full"],
|
|
163476
163585
|
// ── lite: extended solo memory surface ────────────────────────────
|
|
163477
163586
|
memorix_graph_context: ["lite", "team", "full"],
|
|
163478
163587
|
memorix_timeline: ["lite", "team", "full"],
|
|
163479
163588
|
memorix_suggest_topic_key: ["lite", "team", "full"],
|
|
163480
|
-
memorix_session_start: ["lite", "team", "full"],
|
|
163481
163589
|
memorix_session_end: ["lite", "team", "full"],
|
|
163482
163590
|
memorix_session_context: ["lite", "team", "full"],
|
|
163483
163591
|
memorix_store_reasoning: ["lite", "team", "full"],
|
|
163484
163592
|
memorix_search_reasoning: ["lite", "team", "full"],
|
|
163485
163593
|
memorix_transfer: ["lite", "team", "full"],
|
|
163486
163594
|
memorix_retention: ["lite", "team", "full"],
|
|
163487
|
-
memorix_media: ["lite", "team", "full"],
|
|
163488
163595
|
// ── team: orchestration coordination surfaces — HTTP default ──────
|
|
163489
163596
|
memorix_dashboard: ["team", "full"],
|
|
163490
163597
|
memorix_knowledge: ["team", "full"],
|
|
@@ -163537,7 +163644,7 @@ function resolveToolProfile(opts) {
|
|
|
163537
163644
|
function describeProfile(profile) {
|
|
163538
163645
|
switch (profile) {
|
|
163539
163646
|
case "micro":
|
|
163540
|
-
return "micro (agent-ready context +
|
|
163647
|
+
return "micro (agent-ready context + bounded recovery/media, ~9 tools)";
|
|
163541
163648
|
case "lite":
|
|
163542
163649
|
return "lite (core memory + sessions, ~18 tools)";
|
|
163543
163650
|
case "team":
|
|
@@ -164022,7 +164129,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
164022
164129
|
};
|
|
164023
164130
|
const server = existingServer ?? new McpServer({
|
|
164024
164131
|
name: "memorix",
|
|
164025
|
-
version: true ? "1.
|
|
164132
|
+
version: true ? "1.4.0" : "1.0.1"
|
|
164026
164133
|
});
|
|
164027
164134
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
164028
164135
|
server.registerTool = ((name, ...args) => {
|
|
@@ -167709,9 +167816,9 @@ ${lines.join("\n")}` }] };
|
|
|
167709
167816
|
"memorix_media",
|
|
167710
167817
|
{
|
|
167711
167818
|
title: "Manage Controlled Media",
|
|
167712
|
-
description: "Use the controlled local media library for an explicit import, attachment, inspection, or MiniMax generation request. Assets stay outside the Git worktree and enter normal memory only when attach is explicitly true. Use the CLI for destructive removal, quota cleanup,
|
|
167819
|
+
description: "Use the controlled local media library for an explicit import, attachment, inspection, or MiniMax generation request. Assets stay outside the Git worktree and enter normal memory only when attach is explicitly true. Use the CLI for destructive removal, quota cleanup, and direct generation. MCP generation is disabled by default and requires MEMORIX_MCP_MEDIA_GENERATION=1 after the operator reviews provider billing.",
|
|
167713
167820
|
inputSchema: {
|
|
167714
|
-
action: z626.enum(["import", "attach", "list", "show", "generate-image", "generate-video", "status"]),
|
|
167821
|
+
action: z626.enum(["import", "attach", "list", "show", "generate-image", "generate-video", "status", "cancel"]),
|
|
167715
167822
|
path: z626.string().optional().describe("Explicit local image/audio/video/PDF path for import."),
|
|
167716
167823
|
assetId: z626.string().optional().describe("Controlled MediaAsset ID for attach/show."),
|
|
167717
167824
|
jobId: z626.string().optional().describe("Durable media job ID for status."),
|
|
@@ -167783,6 +167890,10 @@ ${lines.join("\n")}` }] };
|
|
|
167783
167890
|
if (!job) throw new Error(`Media job not found: ${jobId}`);
|
|
167784
167891
|
return result({ action, projectId: project.id, job });
|
|
167785
167892
|
}
|
|
167893
|
+
if (action === "cancel") {
|
|
167894
|
+
if (!jobId?.trim()) throw new Error("jobId is required for media cancel");
|
|
167895
|
+
return result({ action, projectId: project.id, job: store.cancelJob(project.id, jobId) });
|
|
167896
|
+
}
|
|
167786
167897
|
if (action === "attach") {
|
|
167787
167898
|
if (!assetId?.trim()) throw new Error("assetId is required for media attach");
|
|
167788
167899
|
const asset = store.getAsset(project.id, assetId);
|