memorix 1.2.6 → 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 +20 -0
- package/README.md +6 -3
- package/README.zh-CN.md +5 -2
- package/dist/cli/index.js +1272 -105
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +7 -2
- package/dist/index.js +809 -52
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +6 -0
- package/dist/maintenance-runner.js +204 -25
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +20 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +809 -52
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +38 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.7-CONTEXT-CONTINUITY.md +68 -0
- package/docs/API_REFERENCE.md +6 -2
- package/docs/dev-log/progress.txt +48 -0
- package/docs/hooks-architecture.md +2 -1
- 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/extensions/memorix.js +17 -0
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/extensions/memorix.js +17 -0
- package/plugins/pi/memorix/package.json +1 -1
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/command-guide.ts +10 -0
- package/src/cli/commands/agent-integrations.ts +102 -5
- package/src/cli/commands/checkpoint.ts +144 -0
- package/src/cli/commands/serve-http.ts +14 -3
- package/src/cli/commands/setup.ts +44 -0
- package/src/cli/index.ts +3 -1
- package/src/codegraph/auto-context.ts +51 -5
- package/src/dashboard/server.ts +11 -0
- package/src/hooks/handler.ts +103 -11
- package/src/hooks/normalizer.ts +85 -1
- package/src/hooks/types.ts +16 -0
- package/src/knowledge/workset.ts +43 -2
- package/src/memory/compaction.ts +165 -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/src/server/tool-profile.ts +1 -0
- package/src/server.ts +94 -0
- package/src/store/compaction-checkpoint-store.ts +397 -0
- package/src/store/sqlite-db.ts +41 -0
- package/src/types.ts +46 -0
|
@@ -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 {
|
|
@@ -229,6 +229,7 @@ function getDatabase(dataDir) {
|
|
|
229
229
|
db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
|
|
230
230
|
db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
|
|
231
231
|
db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
|
|
232
|
+
db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
|
|
232
233
|
try {
|
|
233
234
|
db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
|
|
234
235
|
} catch {
|
|
@@ -278,7 +279,7 @@ function closeAllDatabases() {
|
|
|
278
279
|
_dbCache.delete(key);
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
|
-
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
|
|
282
|
+
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
|
|
282
283
|
var init_sqlite_db = __esm({
|
|
283
284
|
"src/store/sqlite-db.ts"() {
|
|
284
285
|
"use strict";
|
|
@@ -730,6 +731,31 @@ CREATE TABLE IF NOT EXISTS maintenance_targets (
|
|
|
730
731
|
data_dir TEXT NOT NULL,
|
|
731
732
|
updated_at INTEGER NOT NULL
|
|
732
733
|
);
|
|
734
|
+
`;
|
|
735
|
+
CREATE_COMPACTION_CHECKPOINTS_TABLE = `
|
|
736
|
+
CREATE TABLE IF NOT EXISTS compaction_checkpoints (
|
|
737
|
+
id TEXT PRIMARY KEY,
|
|
738
|
+
project_id TEXT NOT NULL,
|
|
739
|
+
session_id TEXT NOT NULL,
|
|
740
|
+
agent TEXT NOT NULL,
|
|
741
|
+
phase TEXT NOT NULL,
|
|
742
|
+
capture_kind TEXT NOT NULL,
|
|
743
|
+
reason TEXT NOT NULL DEFAULT 'unknown',
|
|
744
|
+
source_event TEXT NOT NULL,
|
|
745
|
+
source_key TEXT NOT NULL,
|
|
746
|
+
summary TEXT,
|
|
747
|
+
tokens_before INTEGER,
|
|
748
|
+
first_kept_entry_id TEXT,
|
|
749
|
+
details_json TEXT NOT NULL DEFAULT '{}',
|
|
750
|
+
transcript_available INTEGER NOT NULL DEFAULT 0,
|
|
751
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
752
|
+
pre_captured_at TEXT NOT NULL,
|
|
753
|
+
completed_at TEXT,
|
|
754
|
+
delivered_at TEXT,
|
|
755
|
+
delivery_count INTEGER NOT NULL DEFAULT 0,
|
|
756
|
+
created_at TEXT NOT NULL,
|
|
757
|
+
updated_at TEXT NOT NULL
|
|
758
|
+
);
|
|
733
759
|
`;
|
|
734
760
|
CREATE_INDEXES = `
|
|
735
761
|
CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
|
|
@@ -777,6 +803,9 @@ CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowl
|
|
|
777
803
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_ready ON maintenance_jobs(status, run_after);
|
|
778
804
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_project ON maintenance_jobs(project_id, status, run_after);
|
|
779
805
|
CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_targets(updated_at);
|
|
806
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
|
|
807
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
|
|
808
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
|
|
780
809
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
|
|
781
810
|
ON maintenance_jobs(project_id, kind, dedupe_key)
|
|
782
811
|
WHERE status IN ('pending', 'running', 'retry');
|
|
@@ -850,6 +879,15 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
|
|
|
850
879
|
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflows_workspace_status ON knowledge_workflows(workspaceId, status, updatedAt DESC)");
|
|
851
880
|
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowledge_workflow_runs(projectId, workflowId, startedAt DESC)");
|
|
852
881
|
}
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
id: "1.2.7-compaction-checkpoints",
|
|
885
|
+
apply: (db2) => {
|
|
886
|
+
db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
|
|
887
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC)");
|
|
888
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC)");
|
|
889
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key)");
|
|
890
|
+
}
|
|
853
891
|
}
|
|
854
892
|
];
|
|
855
893
|
_dbCache = /* @__PURE__ */ new Map();
|
|
@@ -5880,6 +5918,10 @@ var init_maintenance_jobs = __esm({
|
|
|
5880
5918
|
LIMIT 1
|
|
5881
5919
|
`).get(input.projectId, input.kind, dedupeKey);
|
|
5882
5920
|
if (existing) {
|
|
5921
|
+
if (input.kind === "vector-backfill" && existing.status === "retry") {
|
|
5922
|
+
this.commit();
|
|
5923
|
+
return rowToJob(existing);
|
|
5924
|
+
}
|
|
5883
5925
|
const nextRunAfter = Math.min(Number(existing.run_after), runAfter);
|
|
5884
5926
|
this.db.prepare(`
|
|
5885
5927
|
UPDATE maintenance_jobs
|
|
@@ -5890,6 +5932,26 @@ var init_maintenance_jobs = __esm({
|
|
|
5890
5932
|
this.commit();
|
|
5891
5933
|
return rowToJob(updated);
|
|
5892
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
|
+
}
|
|
5893
5955
|
const id = randomUUID2();
|
|
5894
5956
|
this.db.prepare(`
|
|
5895
5957
|
INSERT INTO maintenance_jobs (
|
|
@@ -6061,15 +6123,42 @@ var init_maintenance_jobs = __esm({
|
|
|
6061
6123
|
const now3 = options.now ?? Date.now();
|
|
6062
6124
|
const delayMs = Number.isFinite(options.delayMs) ? Math.max(0, Math.floor(options.delayMs)) : 0;
|
|
6063
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;
|
|
6064
6129
|
this.db.prepare(`
|
|
6065
6130
|
UPDATE maintenance_jobs
|
|
6066
|
-
SET status =
|
|
6131
|
+
SET status = ?, run_after = ?, attempts = CASE WHEN ? THEN 0 ELSE attempts END,
|
|
6067
6132
|
payload_json = COALESCE(?, payload_json),
|
|
6133
|
+
last_error = CASE
|
|
6134
|
+
WHEN ? THEN NULL
|
|
6135
|
+
WHEN ? THEN ?
|
|
6136
|
+
ELSE last_error
|
|
6137
|
+
END,
|
|
6068
6138
|
lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
|
|
6069
6139
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
6070
|
-
`).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
|
+
);
|
|
6071
6152
|
return this.get(id);
|
|
6072
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
|
+
}
|
|
6073
6162
|
fail(id, workerId, error, now3 = Date.now()) {
|
|
6074
6163
|
this.begin();
|
|
6075
6164
|
try {
|
|
@@ -6165,6 +6254,9 @@ var init_maintenance_jobs = __esm({
|
|
|
6165
6254
|
delayMs: result.delayMs,
|
|
6166
6255
|
resetAttempts: result.resetAttempts,
|
|
6167
6256
|
payload: result.payload,
|
|
6257
|
+
status: result.status,
|
|
6258
|
+
lastError: result.lastError,
|
|
6259
|
+
clearLastError: result.clearLastError,
|
|
6168
6260
|
now: now3
|
|
6169
6261
|
});
|
|
6170
6262
|
return { state: "rescheduled", job: updated2 };
|
|
@@ -7124,6 +7216,9 @@ function normalizeEmbeddingFailure(error) {
|
|
|
7124
7216
|
message: raw
|
|
7125
7217
|
};
|
|
7126
7218
|
}
|
|
7219
|
+
function vectorBackfillError(error) {
|
|
7220
|
+
return sanitizeCredentials(normalizeEmbeddingFailure(error).message).slice(0, 1e3);
|
|
7221
|
+
}
|
|
7127
7222
|
function queueVectorBackfill(projectId) {
|
|
7128
7223
|
const dataDir = projectDir;
|
|
7129
7224
|
if (!dataDir) return;
|
|
@@ -7879,17 +7974,53 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7879
7974
|
let succeeded = 0;
|
|
7880
7975
|
let failed = 0;
|
|
7881
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
|
+
}
|
|
7882
7993
|
try {
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
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));
|
|
7888
8009
|
}
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
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 {
|
|
7893
8024
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7894
8025
|
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7895
8026
|
}
|
|
@@ -7898,7 +8029,7 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7898
8029
|
console.error(
|
|
7899
8030
|
`[memorix] Backfill embedding mismatch for obs-${id}: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d (kept in queue)`
|
|
7900
8031
|
);
|
|
7901
|
-
|
|
8032
|
+
recordFailure(`dimension mismatch: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d`);
|
|
7902
8033
|
failed++;
|
|
7903
8034
|
continue;
|
|
7904
8035
|
}
|
|
@@ -7937,15 +8068,10 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7937
8068
|
await insertObservation(doc);
|
|
7938
8069
|
vectorMissingIds.delete(id);
|
|
7939
8070
|
succeeded++;
|
|
7940
|
-
}
|
|
7941
|
-
|
|
7942
|
-
} else {
|
|
7943
|
-
lastFailure = "embedding provider unavailable";
|
|
8071
|
+
} catch (error) {
|
|
8072
|
+
recordFailure(vectorBackfillError(error));
|
|
7944
8073
|
failed++;
|
|
7945
8074
|
}
|
|
7946
|
-
} catch (err) {
|
|
7947
|
-
lastFailure = err instanceof Error ? err.message : String(err);
|
|
7948
|
-
failed++;
|
|
7949
8075
|
}
|
|
7950
8076
|
}
|
|
7951
8077
|
} finally {
|
|
@@ -7958,7 +8084,12 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7958
8084
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7959
8085
|
};
|
|
7960
8086
|
}
|
|
7961
|
-
return {
|
|
8087
|
+
return {
|
|
8088
|
+
attempted: ids.length,
|
|
8089
|
+
succeeded,
|
|
8090
|
+
failed,
|
|
8091
|
+
...lastFailure ? { lastError: lastFailure } : {}
|
|
8092
|
+
};
|
|
7962
8093
|
}
|
|
7963
8094
|
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
7964
8095
|
var init_observations = __esm({
|
|
@@ -11877,6 +12008,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
11877
12008
|
|
|
11878
12009
|
// src/runtime/project-maintenance.ts
|
|
11879
12010
|
init_esm_shims();
|
|
12011
|
+
init_maintenance_jobs();
|
|
11880
12012
|
init_lifecycle();
|
|
11881
12013
|
init_timeout();
|
|
11882
12014
|
var DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
@@ -11886,12 +12018,34 @@ var DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
|
|
|
11886
12018
|
var DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
|
|
11887
12019
|
var DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
|
|
11888
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;
|
|
11889
12023
|
var DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
11890
12024
|
function vectorBatchSize(payload) {
|
|
11891
12025
|
const value = payload.limit;
|
|
11892
12026
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_VECTOR_BATCH_SIZE;
|
|
11893
12027
|
return Math.min(100, Math.max(1, Math.floor(value)));
|
|
11894
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
|
+
}
|
|
11895
12049
|
function retentionBatchSize(payload) {
|
|
11896
12050
|
const value = payload.limit;
|
|
11897
12051
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RETENTION_BATCH_SIZE;
|
|
@@ -12300,20 +12454,45 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
|
|
|
12300
12454
|
if (isEmbeddingExplicitlyDisabled2()) return { action: "complete" };
|
|
12301
12455
|
const { backfillVectorEmbeddings: backfillVectorEmbeddings2, getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
12302
12456
|
const before = getVectorStatus2(projectId);
|
|
12303
|
-
if (before.missing === 0)
|
|
12457
|
+
if (before.missing === 0) {
|
|
12458
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
12459
|
+
return { action: "complete" };
|
|
12460
|
+
}
|
|
12304
12461
|
const result = await backfillVectorEmbeddings2({
|
|
12305
12462
|
projectId,
|
|
12306
12463
|
limit: vectorBatchSize(job.payload)
|
|
12307
12464
|
});
|
|
12308
12465
|
const after = getVectorStatus2(projectId);
|
|
12309
|
-
if (after.missing === 0)
|
|
12466
|
+
if (after.missing === 0) {
|
|
12467
|
+
resolveSupersededVectorFailures(projectDir2, projectId);
|
|
12468
|
+
return { action: "complete" };
|
|
12469
|
+
}
|
|
12310
12470
|
if (result.failed > 0 && result.succeeded === 0) {
|
|
12311
|
-
|
|
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
|
+
};
|
|
12312
12485
|
}
|
|
12486
|
+
const priorFailureStreak = vectorBackfillFailureStreak(job.payload);
|
|
12487
|
+
const payload = withoutVectorBackfillFailureStreak(job.payload);
|
|
12313
12488
|
return {
|
|
12314
12489
|
action: "reschedule",
|
|
12315
12490
|
delayMs: result.attempted === 0 ? VECTOR_RETRY_DELAY_MS : 0,
|
|
12316
|
-
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
|
+
} : {}
|
|
12317
12496
|
};
|
|
12318
12497
|
};
|
|
12319
12498
|
}
|