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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.8] - 2026-07-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Recoverable vector backfill** -- Temporary embedding or index failures no longer exhaust a shared background job. Memorix keeps one diagnosable retry with bounded backoff, batches provider requests, and lets a later healthy MCP session resume the same recovery work instead of accumulating permanent failures.
|
|
9
|
+
- **Accurate vector status outside MCP** -- The standalone dashboard and HTTP control plane no longer present an unhydrated in-memory index as a healthy `0 / 0` result. They now explicitly say that vector status belongs to the active MCP session when they cannot observe it.
|
|
10
|
+
- **Codex plugin ownership** -- Agent Doctor recognizes the enabled Codex plugin as the owner of `memorix serve`, reports first-use hook approval as a host consent step instead of a broken install, and avoids suggesting a redundant config repair.
|
|
11
|
+
- **Safe Codex migration** -- `memorix setup --agent codex --global` removes only the known old source-path Memorix MCP entry after the official plugin installation succeeds. Custom user-managed MCP entries remain untouched.
|
|
12
|
+
|
|
5
13
|
## [1.2.7] - 2026-07-27
|
|
6
14
|
|
|
7
15
|
### Added
|
package/dist/cli/index.js
CHANGED
|
@@ -3212,7 +3212,7 @@ ${import_picocolors.default.gray(m3)} ${s2}
|
|
|
3212
3212
|
|
|
3213
3213
|
// src/cli/version.ts
|
|
3214
3214
|
function getCliVersion() {
|
|
3215
|
-
return true ? "1.2.
|
|
3215
|
+
return true ? "1.2.8" : pkg.version;
|
|
3216
3216
|
}
|
|
3217
3217
|
var init_version = __esm({
|
|
3218
3218
|
"src/cli/version.ts"() {
|
|
@@ -17184,6 +17184,10 @@ var init_maintenance_jobs = __esm({
|
|
|
17184
17184
|
LIMIT 1
|
|
17185
17185
|
`).get(input.projectId, input.kind, dedupeKey);
|
|
17186
17186
|
if (existing) {
|
|
17187
|
+
if (input.kind === "vector-backfill" && existing.status === "retry") {
|
|
17188
|
+
this.commit();
|
|
17189
|
+
return rowToJob(existing);
|
|
17190
|
+
}
|
|
17187
17191
|
const nextRunAfter = Math.min(Number(existing.run_after), runAfter);
|
|
17188
17192
|
this.db.prepare(`
|
|
17189
17193
|
UPDATE maintenance_jobs
|
|
@@ -17194,6 +17198,26 @@ var init_maintenance_jobs = __esm({
|
|
|
17194
17198
|
this.commit();
|
|
17195
17199
|
return rowToJob(updated);
|
|
17196
17200
|
}
|
|
17201
|
+
if (input.kind === "vector-backfill") {
|
|
17202
|
+
const failed = this.db.prepare(`
|
|
17203
|
+
SELECT * FROM maintenance_jobs
|
|
17204
|
+
WHERE project_id = ? AND kind = ? AND dedupe_key = ? AND status = 'failed'
|
|
17205
|
+
ORDER BY updated_at DESC
|
|
17206
|
+
LIMIT 1
|
|
17207
|
+
`).get(input.projectId, input.kind, dedupeKey);
|
|
17208
|
+
if (failed) {
|
|
17209
|
+
this.db.prepare(`
|
|
17210
|
+
UPDATE maintenance_jobs
|
|
17211
|
+
SET status = 'pending', attempts = 0, max_attempts = ?, run_after = ?,
|
|
17212
|
+
payload_json = ?, lease_owner = NULL, lease_expires_at = NULL,
|
|
17213
|
+
last_error = NULL, completed_at = NULL, updated_at = ?
|
|
17214
|
+
WHERE id = ?
|
|
17215
|
+
`).run(maxAttempts, runAfter, payloadJson, now3, failed.id);
|
|
17216
|
+
const revived = this.getRow(failed.id);
|
|
17217
|
+
this.commit();
|
|
17218
|
+
return rowToJob(revived);
|
|
17219
|
+
}
|
|
17220
|
+
}
|
|
17197
17221
|
const id = randomUUID2();
|
|
17198
17222
|
this.db.prepare(`
|
|
17199
17223
|
INSERT INTO maintenance_jobs (
|
|
@@ -17365,15 +17389,42 @@ var init_maintenance_jobs = __esm({
|
|
|
17365
17389
|
const now3 = options2.now ?? Date.now();
|
|
17366
17390
|
const delayMs = Number.isFinite(options2.delayMs) ? Math.max(0, Math.floor(options2.delayMs)) : 0;
|
|
17367
17391
|
const payloadJson = options2.payload === void 0 ? null : JSON.stringify(options2.payload);
|
|
17392
|
+
const status = options2.status === "retry" ? "retry" : "pending";
|
|
17393
|
+
const hasLastError = options2.lastError !== void 0;
|
|
17394
|
+
const lastError = hasLastError ? errorText(options2.lastError) : null;
|
|
17368
17395
|
this.db.prepare(`
|
|
17369
17396
|
UPDATE maintenance_jobs
|
|
17370
|
-
SET status =
|
|
17397
|
+
SET status = ?, run_after = ?, attempts = CASE WHEN ? THEN 0 ELSE attempts END,
|
|
17371
17398
|
payload_json = COALESCE(?, payload_json),
|
|
17399
|
+
last_error = CASE
|
|
17400
|
+
WHEN ? THEN NULL
|
|
17401
|
+
WHEN ? THEN ?
|
|
17402
|
+
ELSE last_error
|
|
17403
|
+
END,
|
|
17372
17404
|
lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
|
|
17373
17405
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
17374
|
-
`).run(
|
|
17406
|
+
`).run(
|
|
17407
|
+
status,
|
|
17408
|
+
now3 + delayMs,
|
|
17409
|
+
options2.resetAttempts ? 1 : 0,
|
|
17410
|
+
payloadJson,
|
|
17411
|
+
options2.clearLastError ? 1 : 0,
|
|
17412
|
+
hasLastError ? 1 : 0,
|
|
17413
|
+
lastError,
|
|
17414
|
+
now3,
|
|
17415
|
+
id,
|
|
17416
|
+
workerId
|
|
17417
|
+
);
|
|
17375
17418
|
return this.get(id);
|
|
17376
17419
|
}
|
|
17420
|
+
resolveFailedVectorBackfills(projectId, dedupeKey = "vector-backfill", now3 = Date.now()) {
|
|
17421
|
+
const result = this.db.prepare(`
|
|
17422
|
+
UPDATE maintenance_jobs
|
|
17423
|
+
SET status = 'completed', completed_at = ?, updated_at = ?
|
|
17424
|
+
WHERE project_id = ? AND kind = 'vector-backfill' AND dedupe_key = ? AND status = 'failed'
|
|
17425
|
+
`).run(now3, now3, projectId, dedupeKey);
|
|
17426
|
+
return Number(result.changes ?? 0);
|
|
17427
|
+
}
|
|
17377
17428
|
fail(id, workerId, error2, now3 = Date.now()) {
|
|
17378
17429
|
this.begin();
|
|
17379
17430
|
try {
|
|
@@ -17469,6 +17520,9 @@ var init_maintenance_jobs = __esm({
|
|
|
17469
17520
|
delayMs: result.delayMs,
|
|
17470
17521
|
resetAttempts: result.resetAttempts,
|
|
17471
17522
|
payload: result.payload,
|
|
17523
|
+
status: result.status,
|
|
17524
|
+
lastError: result.lastError,
|
|
17525
|
+
clearLastError: result.clearLastError,
|
|
17472
17526
|
now: now3
|
|
17473
17527
|
});
|
|
17474
17528
|
return { state: "rescheduled", job: updated2 };
|
|
@@ -18447,6 +18501,9 @@ function normalizeEmbeddingFailure(error2) {
|
|
|
18447
18501
|
message: raw
|
|
18448
18502
|
};
|
|
18449
18503
|
}
|
|
18504
|
+
function vectorBackfillError(error2) {
|
|
18505
|
+
return sanitizeCredentials(normalizeEmbeddingFailure(error2).message).slice(0, 1e3);
|
|
18506
|
+
}
|
|
18450
18507
|
function queueVectorBackfill(projectId) {
|
|
18451
18508
|
const dataDir = projectDir;
|
|
18452
18509
|
if (!dataDir) return;
|
|
@@ -19202,17 +19259,53 @@ async function backfillVectorEmbeddings(options2 = {}) {
|
|
|
19202
19259
|
let succeeded = 0;
|
|
19203
19260
|
let failed = 0;
|
|
19204
19261
|
let lastFailure;
|
|
19262
|
+
const recordFailure = (message) => {
|
|
19263
|
+
if (!lastFailure) lastFailure = message;
|
|
19264
|
+
};
|
|
19265
|
+
const candidates = [];
|
|
19266
|
+
for (const id of ids) {
|
|
19267
|
+
const observation = observationById.get(id);
|
|
19268
|
+
if (!observation) {
|
|
19269
|
+
vectorMissingIds.delete(id);
|
|
19270
|
+
continue;
|
|
19271
|
+
}
|
|
19272
|
+
candidates.push({
|
|
19273
|
+
id,
|
|
19274
|
+
observation,
|
|
19275
|
+
text: [observation.title, observation.narrative, ...observation.facts].join(" ")
|
|
19276
|
+
});
|
|
19277
|
+
}
|
|
19205
19278
|
try {
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19279
|
+
if (candidates.length === 0) {
|
|
19280
|
+
return { attempted: ids.length, succeeded, failed };
|
|
19281
|
+
}
|
|
19282
|
+
let embeddings = null;
|
|
19283
|
+
try {
|
|
19284
|
+
const provider2 = await getEmbeddingProvider();
|
|
19285
|
+
if (!provider2) {
|
|
19286
|
+
if (isEmbeddingExplicitlyDisabled()) {
|
|
19287
|
+
for (const candidate2 of candidates) vectorMissingIds.delete(candidate2.id);
|
|
19288
|
+
} else {
|
|
19289
|
+
recordFailure("embedding provider unavailable");
|
|
19290
|
+
failed += candidates.length;
|
|
19291
|
+
}
|
|
19292
|
+
} else {
|
|
19293
|
+
embeddings = await provider2.embedBatch(candidates.map((candidate2) => candidate2.text));
|
|
19211
19294
|
}
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19295
|
+
} catch (error2) {
|
|
19296
|
+
recordFailure(vectorBackfillError(error2));
|
|
19297
|
+
failed += candidates.length;
|
|
19298
|
+
}
|
|
19299
|
+
if (embeddings) {
|
|
19300
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
19301
|
+
const { id, observation: obs } = candidates[index];
|
|
19302
|
+
const embedding = embeddings[index];
|
|
19303
|
+
if (!embedding || embedding.length === 0) {
|
|
19304
|
+
recordFailure("embedding provider returned no vector");
|
|
19305
|
+
failed++;
|
|
19306
|
+
continue;
|
|
19307
|
+
}
|
|
19308
|
+
try {
|
|
19216
19309
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
19217
19310
|
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
19218
19311
|
}
|
|
@@ -19221,7 +19314,7 @@ async function backfillVectorEmbeddings(options2 = {}) {
|
|
|
19221
19314
|
console.error(
|
|
19222
19315
|
`[memorix] Backfill embedding mismatch for obs-${id}: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d (kept in queue)`
|
|
19223
19316
|
);
|
|
19224
|
-
|
|
19317
|
+
recordFailure(`dimension mismatch: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d`);
|
|
19225
19318
|
failed++;
|
|
19226
19319
|
continue;
|
|
19227
19320
|
}
|
|
@@ -19260,15 +19353,10 @@ async function backfillVectorEmbeddings(options2 = {}) {
|
|
|
19260
19353
|
await insertObservation(doc);
|
|
19261
19354
|
vectorMissingIds.delete(id);
|
|
19262
19355
|
succeeded++;
|
|
19263
|
-
}
|
|
19264
|
-
|
|
19265
|
-
} else {
|
|
19266
|
-
lastFailure = "embedding provider unavailable";
|
|
19356
|
+
} catch (error2) {
|
|
19357
|
+
recordFailure(vectorBackfillError(error2));
|
|
19267
19358
|
failed++;
|
|
19268
19359
|
}
|
|
19269
|
-
} catch (err) {
|
|
19270
|
-
lastFailure = err instanceof Error ? err.message : String(err);
|
|
19271
|
-
failed++;
|
|
19272
19360
|
}
|
|
19273
19361
|
}
|
|
19274
19362
|
} finally {
|
|
@@ -19281,7 +19369,12 @@ async function backfillVectorEmbeddings(options2 = {}) {
|
|
|
19281
19369
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19282
19370
|
};
|
|
19283
19371
|
}
|
|
19284
|
-
return {
|
|
19372
|
+
return {
|
|
19373
|
+
attempted: ids.length,
|
|
19374
|
+
succeeded,
|
|
19375
|
+
failed,
|
|
19376
|
+
...lastFailure ? { lastError: lastFailure } : {}
|
|
19377
|
+
};
|
|
19285
19378
|
}
|
|
19286
19379
|
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
19287
19380
|
var init_observations = __esm({
|
|
@@ -27569,6 +27662,7 @@ __export(setup_exports, {
|
|
|
27569
27662
|
installOpenClawBundlePackage: () => installOpenClawBundlePackage,
|
|
27570
27663
|
installPiPackage: () => installPiPackage,
|
|
27571
27664
|
installPluginPackage: () => installPluginPackage,
|
|
27665
|
+
removeLegacyCodexMemorixMcpConfig: () => removeLegacyCodexMemorixMcpConfig,
|
|
27572
27666
|
tryInstallCodexPlugin: () => tryInstallCodexPlugin
|
|
27573
27667
|
});
|
|
27574
27668
|
import { spawnSync } from "child_process";
|
|
@@ -27816,6 +27910,31 @@ function mergeTomlMcpConfig(existingContent, generatedContent) {
|
|
|
27816
27910
|
return `${parts.join("\n\n")}
|
|
27817
27911
|
`;
|
|
27818
27912
|
}
|
|
27913
|
+
function isLegacyCodexMemorixNodeServer(server) {
|
|
27914
|
+
if (server.name !== "memorix" || server.command.trim().toLowerCase() !== "node") return false;
|
|
27915
|
+
const startsMemorixStdio = server.args.some((arg) => arg.trim().toLowerCase() === "serve");
|
|
27916
|
+
const sourceEntry = server.args.some((arg) => {
|
|
27917
|
+
const normalized = arg.replace(/\\/g, "/").toLowerCase();
|
|
27918
|
+
return /\/memorix\/dist\/cli\/index\.(?:[cm]?js)$/.test(normalized);
|
|
27919
|
+
});
|
|
27920
|
+
return startsMemorixStdio && sourceEntry;
|
|
27921
|
+
}
|
|
27922
|
+
async function removeLegacyCodexMemorixMcpConfig(configPath = path17.join(homedir24(), ".codex", "config.toml")) {
|
|
27923
|
+
let existingContent;
|
|
27924
|
+
try {
|
|
27925
|
+
existingContent = await readFile5(configPath, "utf-8");
|
|
27926
|
+
} catch {
|
|
27927
|
+
return { configPath, removed: false };
|
|
27928
|
+
}
|
|
27929
|
+
const adapter = new CodexMCPAdapter();
|
|
27930
|
+
const legacyServer = adapter.parse(existingContent).find(isLegacyCodexMemorixNodeServer);
|
|
27931
|
+
if (!legacyServer) return { configPath, removed: false };
|
|
27932
|
+
let nextContent = removeTomlSection(existingContent, "mcp_servers.memorix.env");
|
|
27933
|
+
nextContent = removeTomlSection(nextContent, "mcp_servers.memorix");
|
|
27934
|
+
await writeFile5(configPath, nextContent ? `${nextContent}
|
|
27935
|
+
` : "", "utf-8");
|
|
27936
|
+
return { configPath, removed: true };
|
|
27937
|
+
}
|
|
27819
27938
|
function mergeYamlMcpConfig(existingContent, generatedContent) {
|
|
27820
27939
|
let existing = {};
|
|
27821
27940
|
try {
|
|
@@ -28297,6 +28416,16 @@ async function installAgentSetup(agent, plan, global2) {
|
|
|
28297
28416
|
const install = tryInstallCodexPlugin();
|
|
28298
28417
|
if (install.ok) v3.success(install.message);
|
|
28299
28418
|
else v3.warn(install.message);
|
|
28419
|
+
if (install.ok) {
|
|
28420
|
+
try {
|
|
28421
|
+
const migration = await removeLegacyCodexMemorixMcpConfig();
|
|
28422
|
+
if (migration.removed) {
|
|
28423
|
+
v3.info(`codex: removed legacy source-path MCP config from ${migration.configPath}`);
|
|
28424
|
+
}
|
|
28425
|
+
} catch {
|
|
28426
|
+
v3.warn("codex: plugin installed, but the legacy MCP config could not be checked");
|
|
28427
|
+
}
|
|
28428
|
+
}
|
|
28300
28429
|
} else if (agent === "claude" && result.marketplaceRoot) {
|
|
28301
28430
|
const install = tryInstallClaudePlugin(result.marketplaceRoot);
|
|
28302
28431
|
if (install.ok) v3.success(install.message);
|
|
@@ -68531,6 +68660,26 @@ function vectorBatchSize(payload) {
|
|
|
68531
68660
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_VECTOR_BATCH_SIZE;
|
|
68532
68661
|
return Math.min(100, Math.max(1, Math.floor(value)));
|
|
68533
68662
|
}
|
|
68663
|
+
function vectorBackfillFailureStreak(payload) {
|
|
68664
|
+
const value = payload.vectorBackfillFailureStreak;
|
|
68665
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.min(16, Math.max(0, Math.floor(value))) : 0;
|
|
68666
|
+
}
|
|
68667
|
+
function vectorBackfillRetryDelayMs(streak) {
|
|
68668
|
+
return Math.min(
|
|
68669
|
+
VECTOR_FAILURE_MAX_RETRY_DELAY_MS,
|
|
68670
|
+
VECTOR_FAILURE_BASE_RETRY_DELAY_MS * 2 ** Math.max(0, streak - 1)
|
|
68671
|
+
);
|
|
68672
|
+
}
|
|
68673
|
+
function withoutVectorBackfillFailureStreak(payload) {
|
|
68674
|
+
const { vectorBackfillFailureStreak: _streak, ...rest } = payload;
|
|
68675
|
+
return rest;
|
|
68676
|
+
}
|
|
68677
|
+
function resolveSupersededVectorFailures(projectDir2, projectId) {
|
|
68678
|
+
try {
|
|
68679
|
+
new MaintenanceJobStore(projectDir2).resolveFailedVectorBackfills(projectId);
|
|
68680
|
+
} catch {
|
|
68681
|
+
}
|
|
68682
|
+
}
|
|
68534
68683
|
function retentionBatchSize(payload) {
|
|
68535
68684
|
const value = payload.limit;
|
|
68536
68685
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RETENTION_BATCH_SIZE;
|
|
@@ -68939,20 +69088,45 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
|
|
|
68939
69088
|
if (isEmbeddingExplicitlyDisabled2()) return { action: "complete" };
|
|
68940
69089
|
const { backfillVectorEmbeddings: backfillVectorEmbeddings2, getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
68941
69090
|
const before = getVectorStatus2(projectId);
|
|
68942
|
-
if (before.missing === 0)
|
|
69091
|
+
if (before.missing === 0) {
|
|
69092
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
69093
|
+
return { action: "complete" };
|
|
69094
|
+
}
|
|
68943
69095
|
const result = await backfillVectorEmbeddings2({
|
|
68944
69096
|
projectId,
|
|
68945
69097
|
limit: vectorBatchSize(job.payload)
|
|
68946
69098
|
});
|
|
68947
69099
|
const after = getVectorStatus2(projectId);
|
|
68948
|
-
if (after.missing === 0)
|
|
69100
|
+
if (after.missing === 0) {
|
|
69101
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
69102
|
+
return { action: "complete" };
|
|
69103
|
+
}
|
|
68949
69104
|
if (result.failed > 0 && result.succeeded === 0) {
|
|
68950
|
-
|
|
69105
|
+
const streak = vectorBackfillFailureStreak(job.payload) + 1;
|
|
69106
|
+
return {
|
|
69107
|
+
action: "reschedule",
|
|
69108
|
+
status: "retry",
|
|
69109
|
+
delayMs: vectorBackfillRetryDelayMs(streak),
|
|
69110
|
+
// This is a recoverable provider/index state, not a terminal job error.
|
|
69111
|
+
// Keeping one retry row prevents new MCP processes from creating a storm.
|
|
69112
|
+
resetAttempts: true,
|
|
69113
|
+
payload: {
|
|
69114
|
+
...job.payload,
|
|
69115
|
+
vectorBackfillFailureStreak: streak
|
|
69116
|
+
},
|
|
69117
|
+
lastError: result.lastError ?? `vector backfill made no progress (${result.failed}/${result.attempted} failed)`
|
|
69118
|
+
};
|
|
68951
69119
|
}
|
|
69120
|
+
const priorFailureStreak = vectorBackfillFailureStreak(job.payload);
|
|
69121
|
+
const payload = withoutVectorBackfillFailureStreak(job.payload);
|
|
68952
69122
|
return {
|
|
68953
69123
|
action: "reschedule",
|
|
68954
69124
|
delayMs: result.attempted === 0 ? VECTOR_RETRY_DELAY_MS : 0,
|
|
68955
|
-
resetAttempts: result.succeeded > 0
|
|
69125
|
+
resetAttempts: result.succeeded > 0,
|
|
69126
|
+
...result.succeeded > 0 ? {
|
|
69127
|
+
...priorFailureStreak > 0 ? { payload } : {},
|
|
69128
|
+
...result.failed > 0 && result.lastError ? { lastError: result.lastError } : priorFailureStreak > 0 ? { clearLastError: true } : {}
|
|
69129
|
+
} : {}
|
|
68956
69130
|
};
|
|
68957
69131
|
};
|
|
68958
69132
|
}
|
|
@@ -68965,11 +69139,12 @@ function createProjectMaintenanceDispatcher(projectId, projectDir2, projectRoot,
|
|
|
68965
69139
|
return isolatedRunner({ job, projectRoot, dataDir: projectDir2 });
|
|
68966
69140
|
};
|
|
68967
69141
|
}
|
|
68968
|
-
var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
|
|
69142
|
+
var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, VECTOR_FAILURE_BASE_RETRY_DELAY_MS, VECTOR_FAILURE_MAX_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
|
|
68969
69143
|
var init_project_maintenance = __esm({
|
|
68970
69144
|
"src/runtime/project-maintenance.ts"() {
|
|
68971
69145
|
"use strict";
|
|
68972
69146
|
init_esm_shims();
|
|
69147
|
+
init_maintenance_jobs();
|
|
68973
69148
|
init_isolated_maintenance();
|
|
68974
69149
|
init_lifecycle();
|
|
68975
69150
|
init_timeout();
|
|
@@ -68980,6 +69155,8 @@ var init_project_maintenance = __esm({
|
|
|
68980
69155
|
DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
|
|
68981
69156
|
DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
|
|
68982
69157
|
VECTOR_RETRY_DELAY_MS = 5e3;
|
|
69158
|
+
VECTOR_FAILURE_BASE_RETRY_DELAY_MS = 6e4;
|
|
69159
|
+
VECTOR_FAILURE_MAX_RETRY_DELAY_MS = 15 * 6e4;
|
|
68983
69160
|
DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
68984
69161
|
}
|
|
68985
69162
|
});
|
|
@@ -69862,6 +70039,17 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
69862
70039
|
sourceCounts,
|
|
69863
70040
|
recentObservations: sorted,
|
|
69864
70041
|
embedding: embeddingStatus,
|
|
70042
|
+
// A standalone dashboard has no access to an active MCP
|
|
70043
|
+
// process's in-memory Orama index. Keep this explicit so the
|
|
70044
|
+
// UI never presents an empty local singleton as a healthy
|
|
70045
|
+
// all-vectors-indexed result.
|
|
70046
|
+
vectorStatus: {
|
|
70047
|
+
available: false,
|
|
70048
|
+
total: 0,
|
|
70049
|
+
missing: 0,
|
|
70050
|
+
missingIds: [],
|
|
70051
|
+
backfillRunning: false
|
|
70052
|
+
},
|
|
69865
70053
|
storage: storageInfo,
|
|
69866
70054
|
maintenance,
|
|
69867
70055
|
...lifecycle ? { lifecycle } : {},
|
|
@@ -71086,7 +71274,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
71086
71274
|
};
|
|
71087
71275
|
const server = existingServer ?? new McpServer({
|
|
71088
71276
|
name: "memorix",
|
|
71089
|
-
version: true ? "1.2.
|
|
71277
|
+
version: true ? "1.2.8" : "1.0.1"
|
|
71090
71278
|
});
|
|
71091
71279
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
71092
71280
|
server.registerTool = ((name, ...args) => {
|
|
@@ -77462,10 +77650,18 @@ var init_serve_http = __esm({
|
|
|
77462
77650
|
};
|
|
77463
77651
|
} catch {
|
|
77464
77652
|
}
|
|
77465
|
-
let vectorStatus = {
|
|
77653
|
+
let vectorStatus = {
|
|
77654
|
+
available: false,
|
|
77655
|
+
total: 0,
|
|
77656
|
+
missing: 0,
|
|
77657
|
+
missingIds: [],
|
|
77658
|
+
backfillRunning: false
|
|
77659
|
+
};
|
|
77466
77660
|
try {
|
|
77467
|
-
const { getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
77468
|
-
|
|
77661
|
+
const { getSearchIndexStatus: getSearchIndexStatus2, getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
77662
|
+
if (getSearchIndexStatus2(statsProjectId).prepared) {
|
|
77663
|
+
vectorStatus = { available: true, ...getVectorStatus2(statsProjectId) };
|
|
77664
|
+
}
|
|
77469
77665
|
} catch {
|
|
77470
77666
|
}
|
|
77471
77667
|
let searchMode = "fulltext";
|
|
@@ -82210,6 +82406,9 @@ function asRecordArray(value) {
|
|
|
82210
82406
|
function codexPluginPath() {
|
|
82211
82407
|
return `${homedir32()}/.codex/plugins/${CODEX_PLUGIN_NAME}`;
|
|
82212
82408
|
}
|
|
82409
|
+
function codexPluginMcpPath() {
|
|
82410
|
+
return `${codexPluginPath()}/.mcp.json`;
|
|
82411
|
+
}
|
|
82213
82412
|
function codexMarketplacePath() {
|
|
82214
82413
|
return `${homedir32()}/.agents/plugins/marketplace.json`;
|
|
82215
82414
|
}
|
|
@@ -82242,6 +82441,7 @@ async function inspectCodexPluginBundle() {
|
|
|
82242
82441
|
const pluginPath = codexPluginPath();
|
|
82243
82442
|
const manifestPath = `${pluginPath}/.codex-plugin/plugin.json`;
|
|
82244
82443
|
const hooksPath = `${pluginPath}/hooks/hooks.json`;
|
|
82444
|
+
const mcpPath = codexPluginMcpPath();
|
|
82245
82445
|
if (!existsSync25(pluginPath)) {
|
|
82246
82446
|
return {
|
|
82247
82447
|
scope: "global",
|
|
@@ -82289,6 +82489,19 @@ async function inspectCodexPluginBundle() {
|
|
|
82289
82489
|
const issues = [];
|
|
82290
82490
|
if (version2 !== getCliVersion()) issues.push("codex-plugin-version-mismatch");
|
|
82291
82491
|
if (manifest.hooks !== "./hooks/hooks.json") issues.push("codex-hook-manifest-missing");
|
|
82492
|
+
if (!existsSync25(mcpPath)) {
|
|
82493
|
+
issues.push("codex-plugin-mcp-manifest-missing");
|
|
82494
|
+
} else {
|
|
82495
|
+
try {
|
|
82496
|
+
const mcpConfig = asRecord2(JSON.parse(await readFile7(mcpPath, "utf-8")));
|
|
82497
|
+
const server = coerceMcpServer("memorix", asRecord2(mcpConfig?.mcpServers)?.memorix);
|
|
82498
|
+
if (!server || !isRecommendedStdioServer(server)) {
|
|
82499
|
+
issues.push("codex-plugin-mcp-manifest-invalid");
|
|
82500
|
+
}
|
|
82501
|
+
} catch {
|
|
82502
|
+
issues.push("codex-plugin-mcp-manifest-unreadable");
|
|
82503
|
+
}
|
|
82504
|
+
}
|
|
82292
82505
|
let declared = [];
|
|
82293
82506
|
if (!existsSync25(hooksPath)) {
|
|
82294
82507
|
issues.push("codex-hook-manifest-missing");
|
|
@@ -82438,7 +82651,9 @@ async function inspectCodexHookTrust() {
|
|
|
82438
82651
|
kind: "hook-trust",
|
|
82439
82652
|
path: configPath,
|
|
82440
82653
|
exists: true,
|
|
82441
|
-
|
|
82654
|
+
// Codex records hook approval only after the user has reviewed it. Memorix
|
|
82655
|
+
// must not treat an intentional, host-owned consent step as a broken setup.
|
|
82656
|
+
status: issues.length > 0 ? "skipped" : "ok",
|
|
82442
82657
|
issues,
|
|
82443
82658
|
hookTrust: { trusted, expected: [...CODEX_HOOK_STATE_NAMES] }
|
|
82444
82659
|
};
|
|
@@ -82506,7 +82721,7 @@ function defaultClaudeProjectKey(projectRoot) {
|
|
|
82506
82721
|
function getClaudeLocalConfigPath() {
|
|
82507
82722
|
return `${homedir32()}/.claude.json`;
|
|
82508
82723
|
}
|
|
82509
|
-
function
|
|
82724
|
+
function coerceMcpServer(name, value) {
|
|
82510
82725
|
const entry = asRecord2(value);
|
|
82511
82726
|
if (!entry) return null;
|
|
82512
82727
|
const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
|
|
@@ -82565,7 +82780,7 @@ async function inspectClaudeLocalMcp(projectRoot) {
|
|
|
82565
82780
|
};
|
|
82566
82781
|
}
|
|
82567
82782
|
const servers = asRecord2(localProject.project.mcpServers);
|
|
82568
|
-
const server =
|
|
82783
|
+
const server = coerceMcpServer("memorix", servers?.memorix);
|
|
82569
82784
|
if (!server) {
|
|
82570
82785
|
return {
|
|
82571
82786
|
scope: "local",
|
|
@@ -82616,6 +82831,56 @@ async function installClaudeLocalMcpConfig(projectRoot) {
|
|
|
82616
82831
|
await writeFile7(configPath, `${JSON.stringify(config2, null, 2)}
|
|
82617
82832
|
`, "utf-8");
|
|
82618
82833
|
}
|
|
82834
|
+
async function inspectCodexPluginMcp() {
|
|
82835
|
+
const runtime = inspectCodexPluginRuntime();
|
|
82836
|
+
if (!runtime.runtime?.installed || !runtime.runtime.enabled) return null;
|
|
82837
|
+
const mcpPath = codexPluginMcpPath();
|
|
82838
|
+
if (!existsSync25(mcpPath)) {
|
|
82839
|
+
return {
|
|
82840
|
+
scope: "global",
|
|
82841
|
+
path: mcpPath,
|
|
82842
|
+
exists: false,
|
|
82843
|
+
status: "repairable",
|
|
82844
|
+
issues: ["codex-plugin-mcp-manifest-missing"],
|
|
82845
|
+
managedByPlugin: true
|
|
82846
|
+
};
|
|
82847
|
+
}
|
|
82848
|
+
try {
|
|
82849
|
+
const config2 = asRecord2(JSON.parse(await readFile7(mcpPath, "utf-8")));
|
|
82850
|
+
const server = coerceMcpServer("memorix", asRecord2(config2?.mcpServers)?.memorix);
|
|
82851
|
+
if (!server) {
|
|
82852
|
+
return {
|
|
82853
|
+
scope: "global",
|
|
82854
|
+
path: mcpPath,
|
|
82855
|
+
exists: true,
|
|
82856
|
+
status: "repairable",
|
|
82857
|
+
issues: ["codex-plugin-mcp-manifest-invalid"],
|
|
82858
|
+
managedByPlugin: true
|
|
82859
|
+
};
|
|
82860
|
+
}
|
|
82861
|
+
const issues = [];
|
|
82862
|
+
if (looksLikeStaleMemorixCommand(server)) issues.push("stale-command-path");
|
|
82863
|
+
if (!isRecommendedStdioServer(server)) issues.push("nonstandard-mcp-command");
|
|
82864
|
+
return {
|
|
82865
|
+
scope: "global",
|
|
82866
|
+
path: mcpPath,
|
|
82867
|
+
exists: true,
|
|
82868
|
+
status: issues.length > 0 ? "repairable" : "ok",
|
|
82869
|
+
issues,
|
|
82870
|
+
managedByPlugin: true,
|
|
82871
|
+
server: sanitizeServer(server)
|
|
82872
|
+
};
|
|
82873
|
+
} catch {
|
|
82874
|
+
return {
|
|
82875
|
+
scope: "global",
|
|
82876
|
+
path: mcpPath,
|
|
82877
|
+
exists: true,
|
|
82878
|
+
status: "repairable",
|
|
82879
|
+
issues: ["codex-plugin-mcp-manifest-unreadable"],
|
|
82880
|
+
managedByPlugin: true
|
|
82881
|
+
};
|
|
82882
|
+
}
|
|
82883
|
+
}
|
|
82619
82884
|
async function inspectMcp(agent, projectRoot, scope) {
|
|
82620
82885
|
if (!isMcpConfigAgent(agent)) {
|
|
82621
82886
|
return { status: "skipped", issues: ["mcp-managed-by-package"], checks: [] };
|
|
@@ -82627,6 +82892,13 @@ async function inspectMcp(agent, projectRoot, scope) {
|
|
|
82627
82892
|
checks.push(await inspectClaudeLocalMcp(projectRoot));
|
|
82628
82893
|
continue;
|
|
82629
82894
|
}
|
|
82895
|
+
if (agent === "codex" && targetScope === "global") {
|
|
82896
|
+
const pluginCheck = await inspectCodexPluginMcp();
|
|
82897
|
+
if (pluginCheck) {
|
|
82898
|
+
checks.push(pluginCheck);
|
|
82899
|
+
continue;
|
|
82900
|
+
}
|
|
82901
|
+
}
|
|
82630
82902
|
const configPath = adapter.getConfigPath(targetScope === "project" ? projectRoot : void 0);
|
|
82631
82903
|
if (targetScope === "global" && configPath === adapter.getConfigPath(projectRoot)) continue;
|
|
82632
82904
|
if (!existsSync25(configPath)) {
|
|
@@ -82771,10 +83043,19 @@ function formatAgentIntegrationReport(report) {
|
|
|
82771
83043
|
lines.push(`${entry.agent}: ${status}`);
|
|
82772
83044
|
if (entry.mcp.issues.length > 0) lines.push(` MCP: ${entry.mcp.issues.join(", ")}`);
|
|
82773
83045
|
if (entry.guidance.issues.length > 0) lines.push(` Guidance: ${entry.guidance.issues.join(", ")}`);
|
|
82774
|
-
|
|
83046
|
+
const hookTrustPending = entry.plugin.issues.includes("codex-hook-trust-pending");
|
|
83047
|
+
const pluginIssues = entry.plugin.issues.filter((issue2) => issue2 !== "codex-hook-trust-pending");
|
|
83048
|
+
if (pluginIssues.length > 0) lines.push(` Plugin: ${pluginIssues.join(", ")}`);
|
|
83049
|
+
if (hookTrustPending) {
|
|
83050
|
+
lines.push(" Hooks: waiting for Codex approval on first use; MCP remains available.");
|
|
83051
|
+
}
|
|
82775
83052
|
}
|
|
82776
83053
|
lines.push("");
|
|
82777
|
-
|
|
83054
|
+
if (report.summary.missing > 0 || report.summary.repairable > 0) {
|
|
83055
|
+
lines.push(`Repair: ${report.repairCommand}`);
|
|
83056
|
+
} else {
|
|
83057
|
+
lines.push("No file repair needed.");
|
|
83058
|
+
}
|
|
82778
83059
|
return lines.join("\n");
|
|
82779
83060
|
}
|
|
82780
83061
|
async function repairAgentIntegrations(options2 = {}) {
|
|
@@ -82788,6 +83069,10 @@ async function repairAgentIntegrations(options2 = {}) {
|
|
|
82788
83069
|
if (isMcpConfigAgent(entry.agent)) {
|
|
82789
83070
|
for (const check2 of entry.mcp.checks) {
|
|
82790
83071
|
if (check2.status === "ok" || check2.status === "skipped") continue;
|
|
83072
|
+
if (check2.managedByPlugin) {
|
|
83073
|
+
skipped.push(`${entry.agent}:mcp:${check2.scope}:plugin-managed`);
|
|
83074
|
+
continue;
|
|
83075
|
+
}
|
|
82791
83076
|
if (check2.status === "missing" && !canInstallMissing) {
|
|
82792
83077
|
skipped.push(`${entry.agent}:mcp:${check2.scope}:missing`);
|
|
82793
83078
|
continue;
|