memorix 1.2.7 → 1.2.8
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 +8 -0
- package/dist/cli/index.js +320 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/memcode.js +0 -0
- package/dist/dashboard/static/app.js +7 -2
- package/dist/index.js +178 -26
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +6 -0
- package/dist/maintenance-runner.js +165 -24
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +8 -0
- package/dist/sdk.js +178 -26
- package/dist/sdk.js.map +1 -1
- package/docs/dev-log/progress.txt +25 -0
- package/package.json +3 -3
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/src/cli/commands/agent-integrations.ts +102 -5
- package/src/cli/commands/serve-http.ts +14 -3
- package/src/cli/commands/setup.ts +44 -0
- package/src/dashboard/server.ts +11 -0
- package/src/memory/observations.ts +67 -20
- package/src/runtime/maintenance-jobs.ts +84 -4
- package/src/runtime/project-maintenance.ts +63 -6
|
@@ -25,6 +25,12 @@ type MaintenanceJobRunResult = {
|
|
|
25
25
|
delayMs: number;
|
|
26
26
|
resetAttempts?: boolean;
|
|
27
27
|
payload?: Record<string, unknown>;
|
|
28
|
+
/** Keep recoverable work out of the immediate pending queue during a cooldown. */
|
|
29
|
+
status?: 'pending' | 'retry';
|
|
30
|
+
/** Persist a sanitized diagnostic without consuming the job's retry budget. */
|
|
31
|
+
lastError?: string;
|
|
32
|
+
/** Clear a stale transient diagnostic after the job makes progress. */
|
|
33
|
+
clearLastError?: boolean;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
interface IsolatedMaintenanceRequest {
|
|
@@ -5918,6 +5918,10 @@ var init_maintenance_jobs = __esm({
|
|
|
5918
5918
|
LIMIT 1
|
|
5919
5919
|
`).get(input.projectId, input.kind, dedupeKey);
|
|
5920
5920
|
if (existing) {
|
|
5921
|
+
if (input.kind === "vector-backfill" && existing.status === "retry") {
|
|
5922
|
+
this.commit();
|
|
5923
|
+
return rowToJob(existing);
|
|
5924
|
+
}
|
|
5921
5925
|
const nextRunAfter = Math.min(Number(existing.run_after), runAfter);
|
|
5922
5926
|
this.db.prepare(`
|
|
5923
5927
|
UPDATE maintenance_jobs
|
|
@@ -5928,6 +5932,26 @@ var init_maintenance_jobs = __esm({
|
|
|
5928
5932
|
this.commit();
|
|
5929
5933
|
return rowToJob(updated);
|
|
5930
5934
|
}
|
|
5935
|
+
if (input.kind === "vector-backfill") {
|
|
5936
|
+
const failed = this.db.prepare(`
|
|
5937
|
+
SELECT * FROM maintenance_jobs
|
|
5938
|
+
WHERE project_id = ? AND kind = ? AND dedupe_key = ? AND status = 'failed'
|
|
5939
|
+
ORDER BY updated_at DESC
|
|
5940
|
+
LIMIT 1
|
|
5941
|
+
`).get(input.projectId, input.kind, dedupeKey);
|
|
5942
|
+
if (failed) {
|
|
5943
|
+
this.db.prepare(`
|
|
5944
|
+
UPDATE maintenance_jobs
|
|
5945
|
+
SET status = 'pending', attempts = 0, max_attempts = ?, run_after = ?,
|
|
5946
|
+
payload_json = ?, lease_owner = NULL, lease_expires_at = NULL,
|
|
5947
|
+
last_error = NULL, completed_at = NULL, updated_at = ?
|
|
5948
|
+
WHERE id = ?
|
|
5949
|
+
`).run(maxAttempts, runAfter, payloadJson, now3, failed.id);
|
|
5950
|
+
const revived = this.getRow(failed.id);
|
|
5951
|
+
this.commit();
|
|
5952
|
+
return rowToJob(revived);
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5931
5955
|
const id = randomUUID2();
|
|
5932
5956
|
this.db.prepare(`
|
|
5933
5957
|
INSERT INTO maintenance_jobs (
|
|
@@ -6099,15 +6123,42 @@ var init_maintenance_jobs = __esm({
|
|
|
6099
6123
|
const now3 = options.now ?? Date.now();
|
|
6100
6124
|
const delayMs = Number.isFinite(options.delayMs) ? Math.max(0, Math.floor(options.delayMs)) : 0;
|
|
6101
6125
|
const payloadJson = options.payload === void 0 ? null : JSON.stringify(options.payload);
|
|
6126
|
+
const status = options.status === "retry" ? "retry" : "pending";
|
|
6127
|
+
const hasLastError = options.lastError !== void 0;
|
|
6128
|
+
const lastError = hasLastError ? errorText(options.lastError) : null;
|
|
6102
6129
|
this.db.prepare(`
|
|
6103
6130
|
UPDATE maintenance_jobs
|
|
6104
|
-
SET status =
|
|
6131
|
+
SET status = ?, run_after = ?, attempts = CASE WHEN ? THEN 0 ELSE attempts END,
|
|
6105
6132
|
payload_json = COALESCE(?, payload_json),
|
|
6133
|
+
last_error = CASE
|
|
6134
|
+
WHEN ? THEN NULL
|
|
6135
|
+
WHEN ? THEN ?
|
|
6136
|
+
ELSE last_error
|
|
6137
|
+
END,
|
|
6106
6138
|
lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
|
|
6107
6139
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
6108
|
-
`).run(
|
|
6140
|
+
`).run(
|
|
6141
|
+
status,
|
|
6142
|
+
now3 + delayMs,
|
|
6143
|
+
options.resetAttempts ? 1 : 0,
|
|
6144
|
+
payloadJson,
|
|
6145
|
+
options.clearLastError ? 1 : 0,
|
|
6146
|
+
hasLastError ? 1 : 0,
|
|
6147
|
+
lastError,
|
|
6148
|
+
now3,
|
|
6149
|
+
id,
|
|
6150
|
+
workerId
|
|
6151
|
+
);
|
|
6109
6152
|
return this.get(id);
|
|
6110
6153
|
}
|
|
6154
|
+
resolveFailedVectorBackfills(projectId, dedupeKey = "vector-backfill", now3 = Date.now()) {
|
|
6155
|
+
const result = this.db.prepare(`
|
|
6156
|
+
UPDATE maintenance_jobs
|
|
6157
|
+
SET status = 'completed', completed_at = ?, updated_at = ?
|
|
6158
|
+
WHERE project_id = ? AND kind = 'vector-backfill' AND dedupe_key = ? AND status = 'failed'
|
|
6159
|
+
`).run(now3, now3, projectId, dedupeKey);
|
|
6160
|
+
return Number(result.changes ?? 0);
|
|
6161
|
+
}
|
|
6111
6162
|
fail(id, workerId, error, now3 = Date.now()) {
|
|
6112
6163
|
this.begin();
|
|
6113
6164
|
try {
|
|
@@ -6203,6 +6254,9 @@ var init_maintenance_jobs = __esm({
|
|
|
6203
6254
|
delayMs: result.delayMs,
|
|
6204
6255
|
resetAttempts: result.resetAttempts,
|
|
6205
6256
|
payload: result.payload,
|
|
6257
|
+
status: result.status,
|
|
6258
|
+
lastError: result.lastError,
|
|
6259
|
+
clearLastError: result.clearLastError,
|
|
6206
6260
|
now: now3
|
|
6207
6261
|
});
|
|
6208
6262
|
return { state: "rescheduled", job: updated2 };
|
|
@@ -7162,6 +7216,9 @@ function normalizeEmbeddingFailure(error) {
|
|
|
7162
7216
|
message: raw
|
|
7163
7217
|
};
|
|
7164
7218
|
}
|
|
7219
|
+
function vectorBackfillError(error) {
|
|
7220
|
+
return sanitizeCredentials(normalizeEmbeddingFailure(error).message).slice(0, 1e3);
|
|
7221
|
+
}
|
|
7165
7222
|
function queueVectorBackfill(projectId) {
|
|
7166
7223
|
const dataDir = projectDir;
|
|
7167
7224
|
if (!dataDir) return;
|
|
@@ -7917,17 +7974,53 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7917
7974
|
let succeeded = 0;
|
|
7918
7975
|
let failed = 0;
|
|
7919
7976
|
let lastFailure;
|
|
7977
|
+
const recordFailure = (message) => {
|
|
7978
|
+
if (!lastFailure) lastFailure = message;
|
|
7979
|
+
};
|
|
7980
|
+
const candidates = [];
|
|
7981
|
+
for (const id of ids) {
|
|
7982
|
+
const observation = observationById.get(id);
|
|
7983
|
+
if (!observation) {
|
|
7984
|
+
vectorMissingIds.delete(id);
|
|
7985
|
+
continue;
|
|
7986
|
+
}
|
|
7987
|
+
candidates.push({
|
|
7988
|
+
id,
|
|
7989
|
+
observation,
|
|
7990
|
+
text: [observation.title, observation.narrative, ...observation.facts].join(" ")
|
|
7991
|
+
});
|
|
7992
|
+
}
|
|
7920
7993
|
try {
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7994
|
+
if (candidates.length === 0) {
|
|
7995
|
+
return { attempted: ids.length, succeeded, failed };
|
|
7996
|
+
}
|
|
7997
|
+
let embeddings = null;
|
|
7998
|
+
try {
|
|
7999
|
+
const provider2 = await getEmbeddingProvider();
|
|
8000
|
+
if (!provider2) {
|
|
8001
|
+
if (isEmbeddingExplicitlyDisabled()) {
|
|
8002
|
+
for (const candidate of candidates) vectorMissingIds.delete(candidate.id);
|
|
8003
|
+
} else {
|
|
8004
|
+
recordFailure("embedding provider unavailable");
|
|
8005
|
+
failed += candidates.length;
|
|
8006
|
+
}
|
|
8007
|
+
} else {
|
|
8008
|
+
embeddings = await provider2.embedBatch(candidates.map((candidate) => candidate.text));
|
|
7926
8009
|
}
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
8010
|
+
} catch (error) {
|
|
8011
|
+
recordFailure(vectorBackfillError(error));
|
|
8012
|
+
failed += candidates.length;
|
|
8013
|
+
}
|
|
8014
|
+
if (embeddings) {
|
|
8015
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
8016
|
+
const { id, observation: obs } = candidates[index];
|
|
8017
|
+
const embedding = embeddings[index];
|
|
8018
|
+
if (!embedding || embedding.length === 0) {
|
|
8019
|
+
recordFailure("embedding provider returned no vector");
|
|
8020
|
+
failed++;
|
|
8021
|
+
continue;
|
|
8022
|
+
}
|
|
8023
|
+
try {
|
|
7931
8024
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7932
8025
|
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7933
8026
|
}
|
|
@@ -7936,7 +8029,7 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7936
8029
|
console.error(
|
|
7937
8030
|
`[memorix] Backfill embedding mismatch for obs-${id}: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d (kept in queue)`
|
|
7938
8031
|
);
|
|
7939
|
-
|
|
8032
|
+
recordFailure(`dimension mismatch: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d`);
|
|
7940
8033
|
failed++;
|
|
7941
8034
|
continue;
|
|
7942
8035
|
}
|
|
@@ -7975,15 +8068,10 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7975
8068
|
await insertObservation(doc);
|
|
7976
8069
|
vectorMissingIds.delete(id);
|
|
7977
8070
|
succeeded++;
|
|
7978
|
-
}
|
|
7979
|
-
|
|
7980
|
-
} else {
|
|
7981
|
-
lastFailure = "embedding provider unavailable";
|
|
8071
|
+
} catch (error) {
|
|
8072
|
+
recordFailure(vectorBackfillError(error));
|
|
7982
8073
|
failed++;
|
|
7983
8074
|
}
|
|
7984
|
-
} catch (err) {
|
|
7985
|
-
lastFailure = err instanceof Error ? err.message : String(err);
|
|
7986
|
-
failed++;
|
|
7987
8075
|
}
|
|
7988
8076
|
}
|
|
7989
8077
|
} finally {
|
|
@@ -7996,7 +8084,12 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7996
8084
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7997
8085
|
};
|
|
7998
8086
|
}
|
|
7999
|
-
return {
|
|
8087
|
+
return {
|
|
8088
|
+
attempted: ids.length,
|
|
8089
|
+
succeeded,
|
|
8090
|
+
failed,
|
|
8091
|
+
...lastFailure ? { lastError: lastFailure } : {}
|
|
8092
|
+
};
|
|
8000
8093
|
}
|
|
8001
8094
|
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
8002
8095
|
var init_observations = __esm({
|
|
@@ -11915,6 +12008,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
11915
12008
|
|
|
11916
12009
|
// src/runtime/project-maintenance.ts
|
|
11917
12010
|
init_esm_shims();
|
|
12011
|
+
init_maintenance_jobs();
|
|
11918
12012
|
init_lifecycle();
|
|
11919
12013
|
init_timeout();
|
|
11920
12014
|
var DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
@@ -11924,12 +12018,34 @@ var DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
|
|
|
11924
12018
|
var DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
|
|
11925
12019
|
var DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
|
|
11926
12020
|
var VECTOR_RETRY_DELAY_MS = 5e3;
|
|
12021
|
+
var VECTOR_FAILURE_BASE_RETRY_DELAY_MS = 6e4;
|
|
12022
|
+
var VECTOR_FAILURE_MAX_RETRY_DELAY_MS = 15 * 6e4;
|
|
11927
12023
|
var DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
11928
12024
|
function vectorBatchSize(payload) {
|
|
11929
12025
|
const value = payload.limit;
|
|
11930
12026
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_VECTOR_BATCH_SIZE;
|
|
11931
12027
|
return Math.min(100, Math.max(1, Math.floor(value)));
|
|
11932
12028
|
}
|
|
12029
|
+
function vectorBackfillFailureStreak(payload) {
|
|
12030
|
+
const value = payload.vectorBackfillFailureStreak;
|
|
12031
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.min(16, Math.max(0, Math.floor(value))) : 0;
|
|
12032
|
+
}
|
|
12033
|
+
function vectorBackfillRetryDelayMs(streak) {
|
|
12034
|
+
return Math.min(
|
|
12035
|
+
VECTOR_FAILURE_MAX_RETRY_DELAY_MS,
|
|
12036
|
+
VECTOR_FAILURE_BASE_RETRY_DELAY_MS * 2 ** Math.max(0, streak - 1)
|
|
12037
|
+
);
|
|
12038
|
+
}
|
|
12039
|
+
function withoutVectorBackfillFailureStreak(payload) {
|
|
12040
|
+
const { vectorBackfillFailureStreak: _streak, ...rest } = payload;
|
|
12041
|
+
return rest;
|
|
12042
|
+
}
|
|
12043
|
+
function resolveSupersededVectorFailures(projectDir2, projectId) {
|
|
12044
|
+
try {
|
|
12045
|
+
new MaintenanceJobStore(projectDir2).resolveFailedVectorBackfills(projectId);
|
|
12046
|
+
} catch {
|
|
12047
|
+
}
|
|
12048
|
+
}
|
|
11933
12049
|
function retentionBatchSize(payload) {
|
|
11934
12050
|
const value = payload.limit;
|
|
11935
12051
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RETENTION_BATCH_SIZE;
|
|
@@ -12338,20 +12454,45 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
|
|
|
12338
12454
|
if (isEmbeddingExplicitlyDisabled2()) return { action: "complete" };
|
|
12339
12455
|
const { backfillVectorEmbeddings: backfillVectorEmbeddings2, getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
12340
12456
|
const before = getVectorStatus2(projectId);
|
|
12341
|
-
if (before.missing === 0)
|
|
12457
|
+
if (before.missing === 0) {
|
|
12458
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
12459
|
+
return { action: "complete" };
|
|
12460
|
+
}
|
|
12342
12461
|
const result = await backfillVectorEmbeddings2({
|
|
12343
12462
|
projectId,
|
|
12344
12463
|
limit: vectorBatchSize(job.payload)
|
|
12345
12464
|
});
|
|
12346
12465
|
const after = getVectorStatus2(projectId);
|
|
12347
|
-
if (after.missing === 0)
|
|
12466
|
+
if (after.missing === 0) {
|
|
12467
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
12468
|
+
return { action: "complete" };
|
|
12469
|
+
}
|
|
12348
12470
|
if (result.failed > 0 && result.succeeded === 0) {
|
|
12349
|
-
|
|
12471
|
+
const streak = vectorBackfillFailureStreak(job.payload) + 1;
|
|
12472
|
+
return {
|
|
12473
|
+
action: "reschedule",
|
|
12474
|
+
status: "retry",
|
|
12475
|
+
delayMs: vectorBackfillRetryDelayMs(streak),
|
|
12476
|
+
// This is a recoverable provider/index state, not a terminal job error.
|
|
12477
|
+
// Keeping one retry row prevents new MCP processes from creating a storm.
|
|
12478
|
+
resetAttempts: true,
|
|
12479
|
+
payload: {
|
|
12480
|
+
...job.payload,
|
|
12481
|
+
vectorBackfillFailureStreak: streak
|
|
12482
|
+
},
|
|
12483
|
+
lastError: result.lastError ?? `vector backfill made no progress (${result.failed}/${result.attempted} failed)`
|
|
12484
|
+
};
|
|
12350
12485
|
}
|
|
12486
|
+
const priorFailureStreak = vectorBackfillFailureStreak(job.payload);
|
|
12487
|
+
const payload = withoutVectorBackfillFailureStreak(job.payload);
|
|
12351
12488
|
return {
|
|
12352
12489
|
action: "reschedule",
|
|
12353
12490
|
delayMs: result.attempted === 0 ? VECTOR_RETRY_DELAY_MS : 0,
|
|
12354
|
-
resetAttempts: result.succeeded > 0
|
|
12491
|
+
resetAttempts: result.succeeded > 0,
|
|
12492
|
+
...result.succeeded > 0 ? {
|
|
12493
|
+
...priorFailureStreak > 0 ? { payload } : {},
|
|
12494
|
+
...result.failed > 0 && result.lastError ? { lastError: result.lastError } : priorFailureStreak > 0 ? { clearLastError: true } : {}
|
|
12495
|
+
} : {}
|
|
12355
12496
|
};
|
|
12356
12497
|
};
|
|
12357
12498
|
}
|