memorix 1.3.3 → 1.3.4
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 +14 -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 +14 -0
- package/dist/sdk.d.ts +5 -513
- package/dist/sdk.js +186 -75
- package/dist/vector-backfill-runner.js +105 -52
- 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
|
@@ -1318,8 +1318,8 @@ async function acquireLock(lockPath) {
|
|
|
1318
1318
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1319
1319
|
if (code === "EEXIST" || code === "EPERM") {
|
|
1320
1320
|
try {
|
|
1321
|
-
const
|
|
1322
|
-
if (Date.now() -
|
|
1321
|
+
const stat4 = await fs2.stat(lockPath);
|
|
1322
|
+
if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
|
|
1323
1323
|
await fs2.unlink(lockPath).catch(() => {
|
|
1324
1324
|
});
|
|
1325
1325
|
continue;
|
|
@@ -7895,6 +7895,10 @@ var init_media_store = __esm({
|
|
|
7895
7895
|
`).run(projectId, assetId).changes ?? 0);
|
|
7896
7896
|
this.db.prepare("DELETE FROM media_derivations WHERE project_id = ? AND asset_id = ?").run(projectId, assetId);
|
|
7897
7897
|
this.db.prepare("DELETE FROM media_embeddings WHERE project_id = ? AND asset_id = ?").run(projectId, assetId);
|
|
7898
|
+
this.db.prepare(`
|
|
7899
|
+
UPDATE media_jobs SET asset_id = NULL, updated_at = ?
|
|
7900
|
+
WHERE project_id = ? AND asset_id = ?
|
|
7901
|
+
`).run(Date.now(), projectId, assetId);
|
|
7898
7902
|
const result = this.db.prepare(`
|
|
7899
7903
|
UPDATE media_assets SET deleted_at = ?, updated_at = ?
|
|
7900
7904
|
WHERE project_id = ? AND id = ? AND deleted_at IS NULL
|
|
@@ -8069,16 +8073,28 @@ var init_media_store = __esm({
|
|
|
8069
8073
|
return row ? rowToJob2(row) : void 0;
|
|
8070
8074
|
}
|
|
8071
8075
|
updateJob(projectId, id, input) {
|
|
8076
|
+
const updated = this.updateJobInternal(projectId, id, input, false);
|
|
8077
|
+
if (!updated) throw new Error(`Media job not found: ${id}`);
|
|
8078
|
+
return updated;
|
|
8079
|
+
}
|
|
8080
|
+
/**
|
|
8081
|
+
* Atomically preserve a user cancellation against a worker that was already
|
|
8082
|
+
* downloading or attaching media when the cancellation arrived.
|
|
8083
|
+
*/
|
|
8084
|
+
updateJobIfNotCancelled(projectId, id, input) {
|
|
8085
|
+
return this.updateJobInternal(projectId, id, input, true);
|
|
8086
|
+
}
|
|
8087
|
+
updateJobInternal(projectId, id, input, rejectCancelled) {
|
|
8072
8088
|
const existing = this.getJob(projectId, id);
|
|
8073
|
-
if (!existing)
|
|
8089
|
+
if (!existing) return void 0;
|
|
8074
8090
|
const now3 = Date.now();
|
|
8075
8091
|
const attempts = existing.attempts + (input.incrementAttempts ? 1 : 0);
|
|
8076
8092
|
const completedAt = input.status === "completed" ? now3 : null;
|
|
8077
|
-
this.db.prepare(`
|
|
8093
|
+
const result = this.db.prepare(`
|
|
8078
8094
|
UPDATE media_jobs
|
|
8079
8095
|
SET status = ?, provider_task_id = COALESCE(?, provider_task_id), asset_id = COALESCE(?, asset_id),
|
|
8080
8096
|
last_error = ?, attempts = ?, updated_at = ?, completed_at = ?
|
|
8081
|
-
WHERE project_id = ? AND id = ?
|
|
8097
|
+
WHERE project_id = ? AND id = ? ${rejectCancelled ? "AND status <> 'cancelled'" : ""}
|
|
8082
8098
|
`).run(
|
|
8083
8099
|
input.status,
|
|
8084
8100
|
input.providerTaskId ?? null,
|
|
@@ -8090,6 +8106,7 @@ var init_media_store = __esm({
|
|
|
8090
8106
|
projectId,
|
|
8091
8107
|
id
|
|
8092
8108
|
);
|
|
8109
|
+
if (Number(result.changes ?? 0) !== 1) return void 0;
|
|
8093
8110
|
return this.getJob(projectId, id);
|
|
8094
8111
|
}
|
|
8095
8112
|
cancelJob(projectId, id) {
|
|
@@ -8135,6 +8152,7 @@ async function attachMediaAssetToObservation(input) {
|
|
|
8135
8152
|
`MIME type: ${asset.mimeType}`,
|
|
8136
8153
|
...input.facts ?? []
|
|
8137
8154
|
];
|
|
8155
|
+
const store = new MediaStore(input.dataDir);
|
|
8138
8156
|
const result = await storeObservation({
|
|
8139
8157
|
entityName: input.entityName?.trim() || sourceLabel2.replace(/\.[^.]+$/, "") || "media-asset",
|
|
8140
8158
|
type: "discovery",
|
|
@@ -8147,13 +8165,15 @@ async function attachMediaAssetToObservation(input) {
|
|
|
8147
8165
|
sourceDetail: "explicit",
|
|
8148
8166
|
...input.visibility ? { visibility: input.visibility } : {},
|
|
8149
8167
|
...input.createdByAgentId ? { createdByAgentId: input.createdByAgentId } : {},
|
|
8150
|
-
...input.visibilityReader ? { visibilityReader: input.visibilityReader } : {}
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8168
|
+
...input.visibilityReader ? { visibilityReader: input.visibilityReader } : {},
|
|
8169
|
+
onPersisted: (observation) => {
|
|
8170
|
+
store.linkAsset({
|
|
8171
|
+
projectId: input.projectId,
|
|
8172
|
+
assetId: asset.id,
|
|
8173
|
+
observationId: observation.id,
|
|
8174
|
+
role: input.role ?? "attachment"
|
|
8175
|
+
});
|
|
8176
|
+
}
|
|
8157
8177
|
});
|
|
8158
8178
|
return result.observation;
|
|
8159
8179
|
}
|
|
@@ -8179,7 +8199,7 @@ var init_types2 = __esm({
|
|
|
8179
8199
|
|
|
8180
8200
|
// src/media/asset-store.ts
|
|
8181
8201
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
8182
|
-
import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, rename, rm, writeFile as writeFile3 } from "fs/promises";
|
|
8202
|
+
import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
|
|
8183
8203
|
import path9 from "path";
|
|
8184
8204
|
function mediaRoot(dataDir) {
|
|
8185
8205
|
return path9.resolve(dataDir, "media");
|
|
@@ -59292,7 +59312,7 @@ var require_node_domexception = __commonJS({
|
|
|
59292
59312
|
// node_modules/fetch-blob/from.js
|
|
59293
59313
|
import { statSync as statSync2, createReadStream, promises as fs7 } from "fs";
|
|
59294
59314
|
import { basename } from "path";
|
|
59295
|
-
var import_node_domexception,
|
|
59315
|
+
var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
|
|
59296
59316
|
var init_from = __esm({
|
|
59297
59317
|
"node_modules/fetch-blob/from.js"() {
|
|
59298
59318
|
"use strict";
|
|
@@ -59300,23 +59320,23 @@ var init_from = __esm({
|
|
|
59300
59320
|
import_node_domexception = __toESM(require_node_domexception(), 1);
|
|
59301
59321
|
init_file();
|
|
59302
59322
|
init_fetch_blob();
|
|
59303
|
-
({ stat } = fs7);
|
|
59323
|
+
({ stat: stat2 } = fs7);
|
|
59304
59324
|
blobFromSync = (path19, type) => fromBlob(statSync2(path19), path19, type);
|
|
59305
|
-
blobFrom = (path19, type) =>
|
|
59306
|
-
fileFrom = (path19, type) =>
|
|
59325
|
+
blobFrom = (path19, type) => stat2(path19).then((stat4) => fromBlob(stat4, path19, type));
|
|
59326
|
+
fileFrom = (path19, type) => stat2(path19).then((stat4) => fromFile(stat4, path19, type));
|
|
59307
59327
|
fileFromSync = (path19, type) => fromFile(statSync2(path19), path19, type);
|
|
59308
|
-
fromBlob = (
|
|
59328
|
+
fromBlob = (stat4, path19, type = "") => new fetch_blob_default([new BlobDataItem({
|
|
59309
59329
|
path: path19,
|
|
59310
|
-
size:
|
|
59311
|
-
lastModified:
|
|
59330
|
+
size: stat4.size,
|
|
59331
|
+
lastModified: stat4.mtimeMs,
|
|
59312
59332
|
start: 0
|
|
59313
59333
|
})], { type });
|
|
59314
|
-
fromFile = (
|
|
59334
|
+
fromFile = (stat4, path19, type = "") => new file_default([new BlobDataItem({
|
|
59315
59335
|
path: path19,
|
|
59316
|
-
size:
|
|
59317
|
-
lastModified:
|
|
59336
|
+
size: stat4.size,
|
|
59337
|
+
lastModified: stat4.mtimeMs,
|
|
59318
59338
|
start: 0
|
|
59319
|
-
})], basename(path19), { type, lastModified:
|
|
59339
|
+
})], basename(path19), { type, lastModified: stat4.mtimeMs });
|
|
59320
59340
|
BlobDataItem = class _BlobDataItem {
|
|
59321
59341
|
#path;
|
|
59322
59342
|
#start;
|
|
@@ -59339,7 +59359,7 @@ var init_from = __esm({
|
|
|
59339
59359
|
});
|
|
59340
59360
|
}
|
|
59341
59361
|
async *stream() {
|
|
59342
|
-
const { mtimeMs } = await
|
|
59362
|
+
const { mtimeMs } = await stat2(this.#path);
|
|
59343
59363
|
if (mtimeMs > this.lastModified) {
|
|
59344
59364
|
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");
|
|
59345
59365
|
}
|
|
@@ -141505,10 +141525,17 @@ function sourceLabel(request, providerTaskId) {
|
|
|
141505
141525
|
}
|
|
141506
141526
|
async function finishWithAsset(input) {
|
|
141507
141527
|
const { dataDir, projectId, store, mediaJob, request, providerTask, dependencies } = input;
|
|
141508
|
-
let
|
|
141528
|
+
let activeJob = store.getJob(projectId, mediaJob.id);
|
|
141529
|
+
if (!activeJob || terminal(activeJob)) return { action: "complete" };
|
|
141530
|
+
let asset = activeJob.assetId ? store.getAsset(projectId, activeJob.assetId) : void 0;
|
|
141509
141531
|
if (!asset) {
|
|
141510
141532
|
if (!providerTask.downloadUrl) throw new Error("Succeeded MiniMax video task did not include a download URL");
|
|
141511
|
-
store.
|
|
141533
|
+
const downloading = store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
141534
|
+
status: "downloading",
|
|
141535
|
+
providerTaskId: providerTask.taskId
|
|
141536
|
+
});
|
|
141537
|
+
if (!downloading) return { action: "complete" };
|
|
141538
|
+
activeJob = downloading;
|
|
141512
141539
|
const bytes = await (dependencies.download ?? ((downloadInput) => downloadProviderMedia(downloadInput)))({
|
|
141513
141540
|
url: providerTask.downloadUrl,
|
|
141514
141541
|
maxBytes: request.maxBytes
|
|
@@ -141525,21 +141552,30 @@ async function finishWithAsset(input) {
|
|
|
141525
141552
|
maxBytes: request.maxBytes
|
|
141526
141553
|
});
|
|
141527
141554
|
asset = imported.asset;
|
|
141555
|
+
const persistedAsset = store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
141556
|
+
status: "downloading",
|
|
141557
|
+
providerTaskId: providerTask.taskId,
|
|
141558
|
+
assetId: asset.id
|
|
141559
|
+
});
|
|
141560
|
+
if (!persistedAsset) return { action: "complete" };
|
|
141561
|
+
activeJob = persistedAsset;
|
|
141528
141562
|
}
|
|
141529
|
-
if (
|
|
141563
|
+
if (activeJob.attachOnComplete) {
|
|
141564
|
+
activeJob = store.getJob(projectId, activeJob.id);
|
|
141565
|
+
if (!activeJob || terminal(activeJob)) return { action: "complete" };
|
|
141530
141566
|
const hasAttachment = store.listLinks(projectId, asset.id).some((link) => link.role === "attachment" && link.observationId !== void 0);
|
|
141531
141567
|
if (!hasAttachment) {
|
|
141532
141568
|
await (dependencies.attach ?? attachMediaAssetToObservation)({
|
|
141533
141569
|
dataDir,
|
|
141534
141570
|
projectId,
|
|
141535
141571
|
asset,
|
|
141536
|
-
title:
|
|
141572
|
+
title: activeJob.observationTitle ?? `MiniMax video: ${asset.sourceLabel ?? asset.id}`,
|
|
141537
141573
|
narrative: `Generated with ${request.provider}/${request.model}. Prompt: ${request.prompt}`,
|
|
141538
141574
|
concepts: ["generated-video", "minimax", request.model]
|
|
141539
141575
|
});
|
|
141540
141576
|
}
|
|
141541
141577
|
}
|
|
141542
|
-
store.
|
|
141578
|
+
store.updateJobIfNotCancelled(projectId, activeJob.id, {
|
|
141543
141579
|
status: "completed",
|
|
141544
141580
|
providerTaskId: providerTask.taskId,
|
|
141545
141581
|
assetId: asset.id
|
|
@@ -141549,10 +141585,12 @@ async function finishWithAsset(input) {
|
|
|
141549
141585
|
function retryAfterProviderError(store, projectId, mediaJob, maintenanceJob, error) {
|
|
141550
141586
|
const message = safeError2(error);
|
|
141551
141587
|
if (maintenanceJob.attempts >= maintenanceJob.maxAttempts) {
|
|
141552
|
-
store.
|
|
141588
|
+
store.updateJobIfNotCancelled(projectId, mediaJob.id, { status: "failed", lastError: message, incrementAttempts: true });
|
|
141589
|
+
return { action: "complete" };
|
|
141590
|
+
}
|
|
141591
|
+
if (!store.updateJobIfNotCancelled(projectId, mediaJob.id, { status: "retry", lastError: message, incrementAttempts: true })) {
|
|
141553
141592
|
return { action: "complete" };
|
|
141554
141593
|
}
|
|
141555
|
-
store.updateJob(projectId, mediaJob.id, { status: "retry", lastError: message, incrementAttempts: true });
|
|
141556
141594
|
return { action: "reschedule", delayMs: MINI_MAX_VIDEO_POLL_MS, status: "retry", lastError: message };
|
|
141557
141595
|
}
|
|
141558
141596
|
function queueMiniMaxVideoGeneration(input) {
|
|
@@ -141593,22 +141631,25 @@ async function runMiniMaxVideoGenerationJob(maintenanceJob, context, dependencie
|
|
|
141593
141631
|
if (mediaJob.kind !== "minimax-video-generation") throw new Error(`Unsupported media job kind: ${mediaJob.kind}`);
|
|
141594
141632
|
const request = parseStoredRequest(mediaJob.request);
|
|
141595
141633
|
if (!mediaJob.providerTaskId) {
|
|
141596
|
-
store.
|
|
141634
|
+
if (!store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "submitting", incrementAttempts: true })) {
|
|
141635
|
+
return { action: "complete" };
|
|
141636
|
+
}
|
|
141597
141637
|
let submitted;
|
|
141598
141638
|
try {
|
|
141599
141639
|
submitted = await (dependencies.createTask ?? createMiniMaxVideoTask)(toVideoRequest(request));
|
|
141600
141640
|
} catch (error) {
|
|
141601
|
-
store.
|
|
141641
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", lastError: safeError2(error) });
|
|
141602
141642
|
return { action: "complete" };
|
|
141603
141643
|
}
|
|
141604
141644
|
if (submitted.status === "failed") {
|
|
141605
|
-
store.
|
|
141645
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", providerTaskId: submitted.taskId, lastError: submitted.error });
|
|
141606
141646
|
return { action: "complete" };
|
|
141607
141647
|
}
|
|
141608
|
-
const pending = store.
|
|
141648
|
+
const pending = store.updateJobIfNotCancelled(context.projectId, mediaJob.id, {
|
|
141609
141649
|
status: submitted.status === "succeeded" ? "downloading" : "provider-pending",
|
|
141610
141650
|
providerTaskId: submitted.taskId
|
|
141611
141651
|
});
|
|
141652
|
+
if (!pending) return { action: "complete" };
|
|
141612
141653
|
if (submitted.status === "succeeded") {
|
|
141613
141654
|
try {
|
|
141614
141655
|
return await finishWithAsset({
|
|
@@ -141636,11 +141677,13 @@ async function runMiniMaxVideoGenerationJob(maintenanceJob, context, dependencie
|
|
|
141636
141677
|
return retryAfterProviderError(store, context.projectId, mediaJob, maintenanceJob, error);
|
|
141637
141678
|
}
|
|
141638
141679
|
if (task.status === "pending") {
|
|
141639
|
-
store.
|
|
141680
|
+
if (!store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "provider-pending", providerTaskId: task.taskId })) {
|
|
141681
|
+
return { action: "complete" };
|
|
141682
|
+
}
|
|
141640
141683
|
return { action: "reschedule", delayMs: MINI_MAX_VIDEO_POLL_MS, resetAttempts: true, clearLastError: true };
|
|
141641
141684
|
}
|
|
141642
141685
|
if (task.status === "failed") {
|
|
141643
|
-
store.
|
|
141686
|
+
store.updateJobIfNotCancelled(context.projectId, mediaJob.id, { status: "failed", providerTaskId: task.taskId, lastError: task.error, incrementAttempts: true });
|
|
141644
141687
|
return { action: "complete" };
|
|
141645
141688
|
}
|
|
141646
141689
|
try {
|
|
@@ -142923,7 +142966,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
|
|
|
142923
142966
|
const rel = normalizeCodePath(relative(projectRoot, abs));
|
|
142924
142967
|
try {
|
|
142925
142968
|
const text = readFileSync6(abs, "utf-8");
|
|
142926
|
-
const
|
|
142969
|
+
const stat4 = fileStat ?? statSync3(abs);
|
|
142927
142970
|
const file = {
|
|
142928
142971
|
id: makeCodeFileId(projectId, rel),
|
|
142929
142972
|
projectId,
|
|
@@ -142932,8 +142975,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
|
|
|
142932
142975
|
contentHash: hashText(text),
|
|
142933
142976
|
// Preserve filesystem precision. Rounding here made same-size edits in
|
|
142934
142977
|
// a short Windows timestamp window indistinguishable to refresh.
|
|
142935
|
-
mtimeMs: Number(
|
|
142936
|
-
sizeBytes: Number(
|
|
142978
|
+
mtimeMs: Number(stat4.mtimeMs),
|
|
142979
|
+
sizeBytes: Number(stat4.size),
|
|
142937
142980
|
indexedAt
|
|
142938
142981
|
};
|
|
142939
142982
|
return {
|
|
@@ -142960,18 +143003,18 @@ async function indexProjectLite(options) {
|
|
|
142960
143003
|
let skippedOversizedFiles = 0;
|
|
142961
143004
|
let unreadableFiles = 0;
|
|
142962
143005
|
for (const abs of paths) {
|
|
142963
|
-
let
|
|
143006
|
+
let stat4;
|
|
142964
143007
|
try {
|
|
142965
|
-
|
|
143008
|
+
stat4 = statSync3(abs);
|
|
142966
143009
|
} catch {
|
|
142967
143010
|
unreadableFiles++;
|
|
142968
143011
|
continue;
|
|
142969
143012
|
}
|
|
142970
|
-
if (Number(
|
|
143013
|
+
if (Number(stat4.size) > maxFileBytes) {
|
|
142971
143014
|
skippedOversizedFiles++;
|
|
142972
143015
|
continue;
|
|
142973
143016
|
}
|
|
142974
|
-
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt,
|
|
143017
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
|
|
142975
143018
|
if (!attempt.delta) {
|
|
142976
143019
|
if (attempt.unreadable) unreadableFiles++;
|
|
142977
143020
|
continue;
|
|
@@ -142999,16 +143042,16 @@ async function refreshProjectLite(store, options) {
|
|
|
142999
143042
|
for (const abs of paths) {
|
|
143000
143043
|
const rel = normalizeCodePath(relative(options.projectRoot, abs));
|
|
143001
143044
|
seenPaths.add(rel);
|
|
143002
|
-
let
|
|
143045
|
+
let stat4;
|
|
143003
143046
|
try {
|
|
143004
|
-
|
|
143047
|
+
stat4 = statSync3(abs);
|
|
143005
143048
|
} catch {
|
|
143006
143049
|
unreadableFiles++;
|
|
143007
143050
|
continue;
|
|
143008
143051
|
}
|
|
143009
143052
|
const existing = existingByPath.get(rel);
|
|
143010
|
-
const mtimeMs = Number(
|
|
143011
|
-
const sizeBytes = Number(
|
|
143053
|
+
const mtimeMs = Number(stat4.mtimeMs);
|
|
143054
|
+
const sizeBytes = Number(stat4.size);
|
|
143012
143055
|
if (sizeBytes > maxFileBytes) {
|
|
143013
143056
|
skippedOversizedFiles++;
|
|
143014
143057
|
if (existing) oversizedExistingFileIds.add(existing.id);
|
|
@@ -143018,7 +143061,7 @@ async function refreshProjectLite(store, options) {
|
|
|
143018
143061
|
unchangedFiles++;
|
|
143019
143062
|
continue;
|
|
143020
143063
|
}
|
|
143021
|
-
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt,
|
|
143064
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
|
|
143022
143065
|
if (!attempt.delta) {
|
|
143023
143066
|
if (attempt.unreadable) unreadableFiles++;
|
|
143024
143067
|
continue;
|
|
@@ -146459,7 +146502,9 @@ async function storeObservation(input) {
|
|
|
146459
146502
|
if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
|
|
146460
146503
|
throw new Error("Cannot update a memory outside this session's write scope.");
|
|
146461
146504
|
}
|
|
146462
|
-
|
|
146505
|
+
const observation2 = await upsertObservation(existing, input, now3);
|
|
146506
|
+
await input.onPersisted?.(observation2);
|
|
146507
|
+
return { observation: observation2, upserted: true };
|
|
146463
146508
|
}
|
|
146464
146509
|
}
|
|
146465
146510
|
const contentForExtraction = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
@@ -146484,6 +146529,7 @@ async function storeObservation(input) {
|
|
|
146484
146529
|
let doc;
|
|
146485
146530
|
let upsertedInsideLock = false;
|
|
146486
146531
|
let reloadCacheAfterCommit = false;
|
|
146532
|
+
let persistedCallbackInvoked = false;
|
|
146487
146533
|
const assignAndPersist = async () => {
|
|
146488
146534
|
if (projectDir) {
|
|
146489
146535
|
const store = getObservationStore();
|
|
@@ -146539,6 +146585,8 @@ async function storeObservation(input) {
|
|
|
146539
146585
|
writeGeneration: transactionGeneration + 1
|
|
146540
146586
|
};
|
|
146541
146587
|
await tx.insert(observation);
|
|
146588
|
+
await input.onPersisted?.(observation);
|
|
146589
|
+
persistedCallbackInvoked = true;
|
|
146542
146590
|
await tx.saveIdCounter(id + 1);
|
|
146543
146591
|
});
|
|
146544
146592
|
observation.writeGeneration = store.getGeneration();
|
|
@@ -146588,6 +146636,8 @@ async function storeObservation(input) {
|
|
|
146588
146636
|
writeGeneration: 0
|
|
146589
146637
|
};
|
|
146590
146638
|
observations.push(observation);
|
|
146639
|
+
await input.onPersisted?.(observation);
|
|
146640
|
+
persistedCallbackInvoked = true;
|
|
146591
146641
|
}
|
|
146592
146642
|
doc = {
|
|
146593
146643
|
id: makeOramaObservationId(input.projectId, observation.id),
|
|
@@ -146619,8 +146669,11 @@ async function storeObservation(input) {
|
|
|
146619
146669
|
};
|
|
146620
146670
|
await assignAndPersist();
|
|
146621
146671
|
if (upsertedInsideLock) {
|
|
146622
|
-
|
|
146672
|
+
const updated = await upsertObservation(observation, input, now3);
|
|
146673
|
+
await input.onPersisted?.(updated);
|
|
146674
|
+
return { observation: updated, upserted: true };
|
|
146623
146675
|
}
|
|
146676
|
+
if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
|
|
146624
146677
|
await bindObservationCodeRefsBestEffort(observation);
|
|
146625
146678
|
queueObservationQualification(observation);
|
|
146626
146679
|
if (resolveObservationVisibility(observation) === "project") {
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.4] - 2026-08-02
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Fail-closed project binding** -- Stdio and HTTP MCP servers no longer restore or keep writing a remembered project root. A launch without a reliable workspace now waits for an explicit root, MCP Roots, or `memorix_session_start`, preventing cross-project memory access.
|
|
9
|
+
- **Portable MCP Roots** -- File URI roots now remain POSIX paths on Linux/macOS and convert correctly on Windows, including drive-letter paths and UNC roots.
|
|
10
|
+
- **Atomic media attachments** -- A media retrieval observation and its asset link now commit in one SQLite transaction. If linking fails, no dangling observation survives. Completed video downloads also persist their local asset before a retryable attachment step.
|
|
11
|
+
- **Authoritative video cancellation** -- A cancellation that lands while a video worker is downloading or finishing can no longer be overwritten back to `completed`; MCP can cancel an existing job even when new MCP media generation is disabled.
|
|
12
|
+
- **Recoverable media cleanup** -- Files Windows cannot immediately release are visibly staged for retry, and media cleanup reports reclaimed or still-pending staged bytes instead of silently implying deletion succeeded.
|
|
13
|
+
- **Non-destructive Codex migration** -- Legacy Codex hook cleanup now preserves customized hook files instead of deleting a file merely because its commands begin with `memorix hook`.
|
|
14
|
+
- **One stdio runtime** -- Direct `node dist/index.js` invocation now delegates to `memorix serve`; importing the package no longer starts an MCP server as a side effect.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Bounded default MCP surface** -- The `micro` profile exposes nine tools: agent-ready context, retrieval, one explicit session recovery path, and one controlled media operation. Advanced tools remain opt-in through higher profiles.
|
|
18
|
+
|
|
5
19
|
## [1.3.3] - 2026-08-02
|
|
6
20
|
|
|
7
21
|
### Added
|