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.
Files changed (53) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +6 -3
  3. package/README.zh-CN.md +5 -2
  4. package/dist/cli/index.js +1272 -105
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/dashboard/static/app.js +7 -2
  7. package/dist/index.js +809 -52
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +6 -0
  10. package/dist/maintenance-runner.js +204 -25
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +20 -0
  13. package/dist/sdk.d.ts +1 -1
  14. package/dist/sdk.js +809 -52
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +38 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.7-CONTEXT-CONTINUITY.md +68 -0
  19. package/docs/API_REFERENCE.md +6 -2
  20. package/docs/dev-log/progress.txt +48 -0
  21. package/docs/hooks-architecture.md +2 -1
  22. package/package.json +3 -3
  23. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  24. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  25. package/plugins/copilot/memorix/plugin.json +1 -1
  26. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  27. package/plugins/omp/memorix/extensions/memorix.js +17 -0
  28. package/plugins/omp/memorix/package.json +1 -1
  29. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  30. package/plugins/pi/memorix/extensions/memorix.js +17 -0
  31. package/plugins/pi/memorix/package.json +1 -1
  32. package/src/cli/capability-map.ts +1 -0
  33. package/src/cli/command-guide.ts +10 -0
  34. package/src/cli/commands/agent-integrations.ts +102 -5
  35. package/src/cli/commands/checkpoint.ts +144 -0
  36. package/src/cli/commands/serve-http.ts +14 -3
  37. package/src/cli/commands/setup.ts +44 -0
  38. package/src/cli/index.ts +3 -1
  39. package/src/codegraph/auto-context.ts +51 -5
  40. package/src/dashboard/server.ts +11 -0
  41. package/src/hooks/handler.ts +103 -11
  42. package/src/hooks/normalizer.ts +85 -1
  43. package/src/hooks/types.ts +16 -0
  44. package/src/knowledge/workset.ts +43 -2
  45. package/src/memory/compaction.ts +165 -0
  46. package/src/memory/observations.ts +67 -20
  47. package/src/runtime/maintenance-jobs.ts +84 -4
  48. package/src/runtime/project-maintenance.ts +63 -6
  49. package/src/server/tool-profile.ts +1 -0
  50. package/src/server.ts +94 -0
  51. package/src/store/compaction-checkpoint-store.ts +397 -0
  52. package/src/store/sqlite-db.ts +41 -0
  53. package/src/types.ts +46 -0
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.6" : pkg.version;
3215
+ return true ? "1.2.8" : pkg.version;
3216
3216
  }
3217
3217
  var init_version = __esm({
3218
3218
  "src/cli/version.ts"() {
@@ -9304,6 +9304,7 @@ function getDatabase(dataDir) {
9304
9304
  db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
9305
9305
  db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
9306
9306
  db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
9307
+ db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
9307
9308
  try {
9308
9309
  db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
9309
9310
  } catch {
@@ -9344,7 +9345,7 @@ function getDatabase(dataDir) {
9344
9345
  _dbCache.set(normalized, db2);
9345
9346
  return db2;
9346
9347
  }
9347
- 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;
9348
+ 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;
9348
9349
  var init_sqlite_db = __esm({
9349
9350
  "src/store/sqlite-db.ts"() {
9350
9351
  "use strict";
@@ -9796,6 +9797,31 @@ CREATE TABLE IF NOT EXISTS maintenance_targets (
9796
9797
  data_dir TEXT NOT NULL,
9797
9798
  updated_at INTEGER NOT NULL
9798
9799
  );
9800
+ `;
9801
+ CREATE_COMPACTION_CHECKPOINTS_TABLE = `
9802
+ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
9803
+ id TEXT PRIMARY KEY,
9804
+ project_id TEXT NOT NULL,
9805
+ session_id TEXT NOT NULL,
9806
+ agent TEXT NOT NULL,
9807
+ phase TEXT NOT NULL,
9808
+ capture_kind TEXT NOT NULL,
9809
+ reason TEXT NOT NULL DEFAULT 'unknown',
9810
+ source_event TEXT NOT NULL,
9811
+ source_key TEXT NOT NULL,
9812
+ summary TEXT,
9813
+ tokens_before INTEGER,
9814
+ first_kept_entry_id TEXT,
9815
+ details_json TEXT NOT NULL DEFAULT '{}',
9816
+ transcript_available INTEGER NOT NULL DEFAULT 0,
9817
+ status TEXT NOT NULL DEFAULT 'active',
9818
+ pre_captured_at TEXT NOT NULL,
9819
+ completed_at TEXT,
9820
+ delivered_at TEXT,
9821
+ delivery_count INTEGER NOT NULL DEFAULT 0,
9822
+ created_at TEXT NOT NULL,
9823
+ updated_at TEXT NOT NULL
9824
+ );
9799
9825
  `;
9800
9826
  CREATE_INDEXES = `
9801
9827
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -9843,6 +9869,9 @@ CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowl
9843
9869
  CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_ready ON maintenance_jobs(status, run_after);
9844
9870
  CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_project ON maintenance_jobs(project_id, status, run_after);
9845
9871
  CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_targets(updated_at);
9872
+ CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
9873
+ CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
9874
+ CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
9846
9875
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
9847
9876
  ON maintenance_jobs(project_id, kind, dedupe_key)
9848
9877
  WHERE status IN ('pending', 'running', 'retry');
@@ -9916,6 +9945,15 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
9916
9945
  db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflows_workspace_status ON knowledge_workflows(workspaceId, status, updatedAt DESC)");
9917
9946
  db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowledge_workflow_runs(projectId, workflowId, startedAt DESC)");
9918
9947
  }
9948
+ },
9949
+ {
9950
+ id: "1.2.7-compaction-checkpoints",
9951
+ apply: (db2) => {
9952
+ db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
9953
+ 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)");
9954
+ 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)");
9955
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key)");
9956
+ }
9919
9957
  }
9920
9958
  ];
9921
9959
  _dbCache = /* @__PURE__ */ new Map();
@@ -17146,6 +17184,10 @@ var init_maintenance_jobs = __esm({
17146
17184
  LIMIT 1
17147
17185
  `).get(input.projectId, input.kind, dedupeKey);
17148
17186
  if (existing) {
17187
+ if (input.kind === "vector-backfill" && existing.status === "retry") {
17188
+ this.commit();
17189
+ return rowToJob(existing);
17190
+ }
17149
17191
  const nextRunAfter = Math.min(Number(existing.run_after), runAfter);
17150
17192
  this.db.prepare(`
17151
17193
  UPDATE maintenance_jobs
@@ -17156,6 +17198,26 @@ var init_maintenance_jobs = __esm({
17156
17198
  this.commit();
17157
17199
  return rowToJob(updated);
17158
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
+ }
17159
17221
  const id = randomUUID2();
17160
17222
  this.db.prepare(`
17161
17223
  INSERT INTO maintenance_jobs (
@@ -17327,15 +17389,42 @@ var init_maintenance_jobs = __esm({
17327
17389
  const now3 = options2.now ?? Date.now();
17328
17390
  const delayMs = Number.isFinite(options2.delayMs) ? Math.max(0, Math.floor(options2.delayMs)) : 0;
17329
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;
17330
17395
  this.db.prepare(`
17331
17396
  UPDATE maintenance_jobs
17332
- SET status = 'pending', run_after = ?, attempts = CASE WHEN ? THEN 0 ELSE attempts END,
17397
+ SET status = ?, run_after = ?, attempts = CASE WHEN ? THEN 0 ELSE attempts END,
17333
17398
  payload_json = COALESCE(?, payload_json),
17399
+ last_error = CASE
17400
+ WHEN ? THEN NULL
17401
+ WHEN ? THEN ?
17402
+ ELSE last_error
17403
+ END,
17334
17404
  lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
17335
17405
  WHERE id = ? AND status = 'running' AND lease_owner = ?
17336
- `).run(now3 + delayMs, options2.resetAttempts ? 1 : 0, payloadJson, now3, id, workerId);
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
+ );
17337
17418
  return this.get(id);
17338
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
+ }
17339
17428
  fail(id, workerId, error2, now3 = Date.now()) {
17340
17429
  this.begin();
17341
17430
  try {
@@ -17431,6 +17520,9 @@ var init_maintenance_jobs = __esm({
17431
17520
  delayMs: result.delayMs,
17432
17521
  resetAttempts: result.resetAttempts,
17433
17522
  payload: result.payload,
17523
+ status: result.status,
17524
+ lastError: result.lastError,
17525
+ clearLastError: result.clearLastError,
17434
17526
  now: now3
17435
17527
  });
17436
17528
  return { state: "rescheduled", job: updated2 };
@@ -18126,10 +18218,10 @@ var init_store = __esm({
18126
18218
  return row ? rowToSnapshot(row) : void 0;
18127
18219
  }
18128
18220
  listSnapshots(projectId, limit = 20) {
18129
- const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(200, Math.floor(limit))) : 20;
18221
+ const safeLimit2 = Number.isFinite(limit) ? Math.max(1, Math.min(200, Math.floor(limit))) : 20;
18130
18222
  return this.db.prepare(
18131
18223
  "SELECT * FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT ?"
18132
- ).all(projectId, safeLimit).map(rowToSnapshot);
18224
+ ).all(projectId, safeLimit2).map(rowToSnapshot);
18133
18225
  }
18134
18226
  /**
18135
18227
  * Record a completed scan and mark all current structural facts with its
@@ -18409,6 +18501,9 @@ function normalizeEmbeddingFailure(error2) {
18409
18501
  message: raw
18410
18502
  };
18411
18503
  }
18504
+ function vectorBackfillError(error2) {
18505
+ return sanitizeCredentials(normalizeEmbeddingFailure(error2).message).slice(0, 1e3);
18506
+ }
18412
18507
  function queueVectorBackfill(projectId) {
18413
18508
  const dataDir = projectDir;
18414
18509
  if (!dataDir) return;
@@ -19164,17 +19259,53 @@ async function backfillVectorEmbeddings(options2 = {}) {
19164
19259
  let succeeded = 0;
19165
19260
  let failed = 0;
19166
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
+ }
19167
19278
  try {
19168
- for (const id of ids) {
19169
- const obs = observationById.get(id);
19170
- if (!obs) {
19171
- vectorMissingIds.delete(id);
19172
- continue;
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));
19173
19294
  }
19174
- const text = [obs.title, obs.narrative, ...obs.facts].join(" ");
19175
- try {
19176
- const embedding = await generateEmbedding(text);
19177
- if (embedding) {
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 {
19178
19309
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
19179
19310
  await upgradeVectorSchemaAfterFirstEmbedding(embedding);
19180
19311
  }
@@ -19183,7 +19314,7 @@ async function backfillVectorEmbeddings(options2 = {}) {
19183
19314
  console.error(
19184
19315
  `[memorix] Backfill embedding mismatch for obs-${id}: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d (kept in queue)`
19185
19316
  );
19186
- lastFailure = `dimension mismatch: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d`;
19317
+ recordFailure(`dimension mismatch: provider returned ${embedding.length}d, index expects ${vectorDimensions ?? "unknown"}d`);
19187
19318
  failed++;
19188
19319
  continue;
19189
19320
  }
@@ -19222,15 +19353,10 @@ async function backfillVectorEmbeddings(options2 = {}) {
19222
19353
  await insertObservation(doc);
19223
19354
  vectorMissingIds.delete(id);
19224
19355
  succeeded++;
19225
- } else if (isEmbeddingExplicitlyDisabled()) {
19226
- vectorMissingIds.delete(id);
19227
- } else {
19228
- lastFailure = "embedding provider unavailable";
19356
+ } catch (error2) {
19357
+ recordFailure(vectorBackfillError(error2));
19229
19358
  failed++;
19230
19359
  }
19231
- } catch (err) {
19232
- lastFailure = err instanceof Error ? err.message : String(err);
19233
- failed++;
19234
19360
  }
19235
19361
  }
19236
19362
  } finally {
@@ -19243,7 +19369,12 @@ async function backfillVectorEmbeddings(options2 = {}) {
19243
19369
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19244
19370
  };
19245
19371
  }
19246
- return { attempted: ids.length, succeeded, failed };
19372
+ return {
19373
+ attempted: ids.length,
19374
+ succeeded,
19375
+ failed,
19376
+ ...lastFailure ? { lastError: lastFailure } : {}
19377
+ };
19247
19378
  }
19248
19379
  var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
19249
19380
  var init_observations = __esm({
@@ -27531,6 +27662,7 @@ __export(setup_exports, {
27531
27662
  installOpenClawBundlePackage: () => installOpenClawBundlePackage,
27532
27663
  installPiPackage: () => installPiPackage,
27533
27664
  installPluginPackage: () => installPluginPackage,
27665
+ removeLegacyCodexMemorixMcpConfig: () => removeLegacyCodexMemorixMcpConfig,
27534
27666
  tryInstallCodexPlugin: () => tryInstallCodexPlugin
27535
27667
  });
27536
27668
  import { spawnSync } from "child_process";
@@ -27778,6 +27910,31 @@ function mergeTomlMcpConfig(existingContent, generatedContent) {
27778
27910
  return `${parts.join("\n\n")}
27779
27911
  `;
27780
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
+ }
27781
27938
  function mergeYamlMcpConfig(existingContent, generatedContent) {
27782
27939
  let existing = {};
27783
27940
  try {
@@ -28259,6 +28416,16 @@ async function installAgentSetup(agent, plan, global2) {
28259
28416
  const install = tryInstallCodexPlugin();
28260
28417
  if (install.ok) v3.success(install.message);
28261
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
+ }
28262
28429
  } else if (agent === "claude" && result.marketplaceRoot) {
28263
28430
  const install = tryInstallClaudePlugin(result.marketplaceRoot);
28264
28431
  if (install.ok) v3.success(install.message);
@@ -34934,7 +35101,7 @@ function renderTaskWorksetPrompt(input) {
34934
35101
  });
34935
35102
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
34936
35103
  const hasContinuation = Boolean(
34937
- input.continuation?.previousSession || (input.continuation?.memories.length ?? 0) > 0
35104
+ input.continuation?.previousSession || (input.continuation?.memories.length ?? 0) > 0 || input.continuation?.compactCheckpoint
34938
35105
  );
34939
35106
  if (hasContinuation && input.continuation) {
34940
35107
  appendLine(lines, "", maxTokens, omitted, "continuation-heading");
@@ -34974,6 +35141,24 @@ function renderTaskWorksetPrompt(input) {
34974
35141
  }
34975
35142
  );
34976
35143
  }
35144
+ if (input.continuation.compactCheckpoint) {
35145
+ const checkpoint = input.continuation.compactCheckpoint;
35146
+ const source = `${checkpoint.agent}, ${checkpoint.captureKind}, ${checkpoint.reason}`;
35147
+ appendLine(
35148
+ lines,
35149
+ "- Recent host compact checkpoint (" + source + "): " + short(checkpoint.summary, 36),
35150
+ maxTokens,
35151
+ omitted,
35152
+ "continuation-compact-checkpoint",
35153
+ selected,
35154
+ {
35155
+ kind: "continuation",
35156
+ id: "compact:" + checkpoint.id,
35157
+ reason: "recent host-native compact lifecycle evidence",
35158
+ trust: "historical"
35159
+ }
35160
+ );
35161
+ }
34977
35162
  }
34978
35163
  if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
34979
35164
  appendLine(lines, "", maxTokens, omitted, "caution-heading");
@@ -35243,7 +35428,7 @@ async function buildTaskWorkset(input) {
35243
35428
  ...input.verificationHints
35244
35429
  ]).slice(0, 4);
35245
35430
  const normalizedCautions = unique(cautions.map((caution) => caution.kind)).map((kind) => cautions.find((caution) => caution.kind === kind)).slice(0, 6);
35246
- const continuation = input.continuation && (input.continuation.previousSession || input.continuation.memories.length > 0) ? {
35431
+ const continuation = input.continuation && (input.continuation.previousSession || input.continuation.memories.length > 0 || input.continuation.compactCheckpoint) ? {
35247
35432
  ...input.continuation.previousSession ? {
35248
35433
  previousSession: {
35249
35434
  ...input.continuation.previousSession,
@@ -35254,7 +35439,13 @@ async function buildTaskWorkset(input) {
35254
35439
  ...memory,
35255
35440
  title: short(memory.title, 20),
35256
35441
  ...memory.detail ? { detail: short(memory.detail, CONTINUATION_DETAIL_TOKEN_BUDGET) } : {}
35257
- }))
35442
+ })),
35443
+ ...input.continuation.compactCheckpoint ? {
35444
+ compactCheckpoint: {
35445
+ ...input.continuation.compactCheckpoint,
35446
+ summary: short(input.continuation.compactCheckpoint.summary, 44)
35447
+ }
35448
+ } : {}
35258
35449
  } : void 0;
35259
35450
  const base = {
35260
35451
  version: "1.2",
@@ -37477,6 +37668,311 @@ var init_session = __esm({
37477
37668
  }
37478
37669
  });
37479
37670
 
37671
+ // src/store/compaction-checkpoint-store.ts
37672
+ var compaction_checkpoint_store_exports = {};
37673
+ __export(compaction_checkpoint_store_exports, {
37674
+ CompactionCheckpointStore: () => CompactionCheckpointStore
37675
+ });
37676
+ import { createHash as createHash10, randomUUID as randomUUID9 } from "crypto";
37677
+ function optionalText5(value) {
37678
+ return typeof value === "string" && value ? value : void 0;
37679
+ }
37680
+ function optionalNumber2(value) {
37681
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
37682
+ }
37683
+ function parseDetails(value) {
37684
+ if (typeof value !== "string" || !value) return void 0;
37685
+ try {
37686
+ const parsed = JSON.parse(value);
37687
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
37688
+ } catch {
37689
+ return void 0;
37690
+ }
37691
+ }
37692
+ function sanitizeSummary(value) {
37693
+ if (!value?.trim()) return void 0;
37694
+ return sanitizeCredentials(value).slice(0, MAX_SUMMARY_CHARS).trim() || void 0;
37695
+ }
37696
+ function sanitizeDetails(value) {
37697
+ if (!value) return void 0;
37698
+ try {
37699
+ const sanitized = sanitizeCredentials(JSON.stringify(value));
37700
+ if (sanitized.length > MAX_DETAILS_CHARS) return { truncated: true };
37701
+ const parsed = JSON.parse(sanitized);
37702
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
37703
+ } catch {
37704
+ return { unavailable: true };
37705
+ }
37706
+ }
37707
+ function sourceKeyFor(input, summary) {
37708
+ if (input.sourceKey?.trim()) return input.sourceKey.trim().slice(0, 300);
37709
+ const material = [
37710
+ input.projectId,
37711
+ input.sessionId,
37712
+ input.agent,
37713
+ input.sourceEvent,
37714
+ summary ?? "",
37715
+ input.tokensBefore ?? "",
37716
+ input.firstKeptEntryId ?? ""
37717
+ ].join("\0");
37718
+ return `derived:${createHash10("sha256").update(material).digest("hex").slice(0, 32)}`;
37719
+ }
37720
+ function rowToCheckpoint(row) {
37721
+ return {
37722
+ id: row.id,
37723
+ projectId: row.project_id,
37724
+ sessionId: row.session_id,
37725
+ agent: row.agent,
37726
+ phase: row.phase,
37727
+ captureKind: row.capture_kind,
37728
+ reason: row.reason,
37729
+ sourceEvent: row.source_event,
37730
+ sourceKey: row.source_key,
37731
+ ...optionalText5(row.summary) ? { summary: row.summary } : {},
37732
+ ...optionalNumber2(row.tokens_before) !== void 0 ? { tokensBefore: Number(row.tokens_before) } : {},
37733
+ ...optionalText5(row.first_kept_entry_id) ? { firstKeptEntryId: row.first_kept_entry_id } : {},
37734
+ ...parseDetails(row.details_json) ? { details: parseDetails(row.details_json) } : {},
37735
+ transcriptAvailable: Boolean(row.transcript_available),
37736
+ status: row.status,
37737
+ preCapturedAt: row.pre_captured_at,
37738
+ ...optionalText5(row.completed_at) ? { completedAt: row.completed_at } : {},
37739
+ ...optionalText5(row.delivered_at) ? { deliveredAt: row.delivered_at } : {},
37740
+ deliveryCount: Number(row.delivery_count ?? 0),
37741
+ createdAt: row.created_at,
37742
+ updatedAt: row.updated_at
37743
+ };
37744
+ }
37745
+ function safeReason(value) {
37746
+ return value === "manual" || value === "auto" ? value : "unknown";
37747
+ }
37748
+ function safeLimit(value, fallback) {
37749
+ if (!Number.isFinite(value) || value == null) return fallback;
37750
+ return Math.max(1, Math.min(500, Math.floor(value)));
37751
+ }
37752
+ var MAX_SUMMARY_CHARS, MAX_DETAILS_CHARS, CompactionCheckpointStore;
37753
+ var init_compaction_checkpoint_store = __esm({
37754
+ "src/store/compaction-checkpoint-store.ts"() {
37755
+ "use strict";
37756
+ init_esm_shims();
37757
+ init_secret_filter();
37758
+ init_sqlite_db();
37759
+ MAX_SUMMARY_CHARS = 24e3;
37760
+ MAX_DETAILS_CHARS = 4e3;
37761
+ CompactionCheckpointStore = class {
37762
+ db;
37763
+ constructor(dataDir) {
37764
+ this.db = getDatabase(dataDir);
37765
+ }
37766
+ recordPreflight(input) {
37767
+ const pending = this.db.prepare(`
37768
+ SELECT * FROM compaction_checkpoints
37769
+ WHERE project_id = ? AND session_id = ? AND agent = ?
37770
+ AND phase = 'pre' AND status = 'active'
37771
+ ORDER BY pre_captured_at DESC, created_at DESC
37772
+ LIMIT 1
37773
+ `).get(input.projectId, input.sessionId, input.agent);
37774
+ if (pending) return rowToCheckpoint(pending);
37775
+ const now3 = input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
37776
+ const checkpoint = {
37777
+ id: randomUUID9(),
37778
+ projectId: input.projectId,
37779
+ sessionId: input.sessionId,
37780
+ agent: input.agent,
37781
+ phase: "pre",
37782
+ captureKind: "preflight",
37783
+ reason: safeReason(input.reason),
37784
+ sourceEvent: input.sourceEvent,
37785
+ sourceKey: `pre:${randomUUID9()}`,
37786
+ transcriptAvailable: Boolean(input.transcriptAvailable),
37787
+ status: "active",
37788
+ preCapturedAt: now3,
37789
+ deliveryCount: 0,
37790
+ createdAt: now3,
37791
+ updatedAt: now3
37792
+ };
37793
+ this.insert(checkpoint);
37794
+ return checkpoint;
37795
+ }
37796
+ complete(input) {
37797
+ const completedAt = input.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
37798
+ const summary = sanitizeSummary(input.summary);
37799
+ const pending = this.db.prepare(`
37800
+ SELECT * FROM compaction_checkpoints
37801
+ WHERE project_id = ? AND session_id = ? AND agent = ?
37802
+ AND phase = 'pre' AND status = 'active'
37803
+ ORDER BY pre_captured_at DESC, created_at DESC
37804
+ LIMIT 1
37805
+ `).get(input.projectId, input.sessionId, input.agent);
37806
+ if (!input.sourceKey?.trim() && !summary && input.tokensBefore === void 0 && !input.firstKeptEntryId && !pending) {
37807
+ const priorLifecycle = this.db.prepare(`
37808
+ SELECT * FROM compaction_checkpoints
37809
+ WHERE project_id = ? AND session_id = ? AND agent = ?
37810
+ AND phase = 'complete' AND capture_kind = 'lifecycle'
37811
+ AND source_event = ? AND status = 'active'
37812
+ ORDER BY completed_at DESC, created_at DESC
37813
+ LIMIT 1
37814
+ `).get(input.projectId, input.sessionId, input.agent, input.sourceEvent);
37815
+ if (priorLifecycle) return rowToCheckpoint(priorLifecycle);
37816
+ }
37817
+ const sourceKey = input.sourceKey?.trim() ? input.sourceKey.trim().slice(0, 300) : pending ? `lifecycle:${pending.id}` : sourceKeyFor(input, summary);
37818
+ const existing = this.db.prepare(`
37819
+ SELECT * FROM compaction_checkpoints
37820
+ WHERE project_id = ? AND source_key = ?
37821
+ LIMIT 1
37822
+ `).get(input.projectId, sourceKey);
37823
+ if (existing) return rowToCheckpoint(existing);
37824
+ const captureKind = summary ? "native-summary" : "lifecycle";
37825
+ const details = sanitizeDetails(input.details);
37826
+ if (pending) {
37827
+ const checkpoint2 = rowToCheckpoint(pending);
37828
+ const nextReason = safeReason(input.reason) === "unknown" ? checkpoint2.reason : safeReason(input.reason);
37829
+ this.db.prepare(`
37830
+ UPDATE compaction_checkpoints
37831
+ SET phase = 'complete', capture_kind = ?, reason = ?, source_event = ?, source_key = ?,
37832
+ summary = ?, tokens_before = ?, first_kept_entry_id = ?, details_json = ?,
37833
+ completed_at = ?, updated_at = ?
37834
+ WHERE id = ?
37835
+ `).run(
37836
+ captureKind,
37837
+ nextReason,
37838
+ input.sourceEvent,
37839
+ sourceKey,
37840
+ summary ?? null,
37841
+ input.tokensBefore ?? null,
37842
+ input.firstKeptEntryId ?? null,
37843
+ JSON.stringify(details ?? {}),
37844
+ completedAt,
37845
+ completedAt,
37846
+ checkpoint2.id
37847
+ );
37848
+ return this.get(checkpoint2.id);
37849
+ }
37850
+ const checkpoint = {
37851
+ id: randomUUID9(),
37852
+ projectId: input.projectId,
37853
+ sessionId: input.sessionId,
37854
+ agent: input.agent,
37855
+ phase: "complete",
37856
+ captureKind,
37857
+ reason: safeReason(input.reason),
37858
+ sourceEvent: input.sourceEvent,
37859
+ sourceKey,
37860
+ ...summary ? { summary } : {},
37861
+ ...input.tokensBefore !== void 0 ? { tokensBefore: input.tokensBefore } : {},
37862
+ ...input.firstKeptEntryId ? { firstKeptEntryId: input.firstKeptEntryId } : {},
37863
+ ...details ? { details } : {},
37864
+ transcriptAvailable: false,
37865
+ status: "active",
37866
+ preCapturedAt: completedAt,
37867
+ completedAt,
37868
+ deliveryCount: 0,
37869
+ createdAt: completedAt,
37870
+ updatedAt: completedAt
37871
+ };
37872
+ this.insert(checkpoint);
37873
+ return checkpoint;
37874
+ }
37875
+ get(id) {
37876
+ const row = this.db.prepare("SELECT * FROM compaction_checkpoints WHERE id = ?").get(id);
37877
+ return row ? rowToCheckpoint(row) : void 0;
37878
+ }
37879
+ list(options2) {
37880
+ const conditions = ["project_id = ?"];
37881
+ const values = [options2.projectId];
37882
+ if (options2.sessionId) {
37883
+ conditions.push("session_id = ?");
37884
+ values.push(options2.sessionId);
37885
+ }
37886
+ if (options2.agent) {
37887
+ conditions.push("agent = ?");
37888
+ values.push(options2.agent);
37889
+ }
37890
+ if (!options2.includeArchived) {
37891
+ conditions.push("status = 'active'");
37892
+ }
37893
+ const rows = this.db.prepare(`
37894
+ SELECT * FROM compaction_checkpoints
37895
+ WHERE ${conditions.join(" AND ")}
37896
+ ORDER BY COALESCE(completed_at, pre_captured_at) DESC, created_at DESC
37897
+ LIMIT ?
37898
+ `).all(...values, safeLimit(options2.limit, 20));
37899
+ return rows.map(rowToCheckpoint);
37900
+ }
37901
+ findUndelivered(projectId, sessionId, agent) {
37902
+ const row = this.db.prepare(`
37903
+ SELECT * FROM compaction_checkpoints
37904
+ WHERE project_id = ? AND session_id = ? AND agent = ?
37905
+ AND phase = 'complete' AND status = 'active' AND delivered_at IS NULL
37906
+ ORDER BY completed_at DESC, created_at DESC
37907
+ LIMIT 1
37908
+ `).get(projectId, sessionId, agent);
37909
+ return row ? rowToCheckpoint(row) : void 0;
37910
+ }
37911
+ findLatestCompleted(projectId, options2 = {}) {
37912
+ const excludeSession = options2.excludeSession;
37913
+ const exclusion = excludeSession ? " AND NOT (session_id = ? AND agent = ? )" : "";
37914
+ const values = excludeSession ? [projectId, excludeSession.sessionId, excludeSession.agent] : [projectId];
37915
+ const row = this.db.prepare(`
37916
+ SELECT * FROM compaction_checkpoints
37917
+ WHERE project_id = ? AND phase = 'complete' AND status = 'active'
37918
+ ${exclusion}
37919
+ ORDER BY completed_at DESC, created_at DESC
37920
+ LIMIT 1
37921
+ `).get(...values);
37922
+ return row ? rowToCheckpoint(row) : void 0;
37923
+ }
37924
+ archive(id, archivedAt = (/* @__PURE__ */ new Date()).toISOString()) {
37925
+ const result = this.db.prepare(`
37926
+ UPDATE compaction_checkpoints
37927
+ SET status = 'archived', updated_at = ?
37928
+ WHERE id = ? AND status = 'active'
37929
+ `).run(archivedAt, id);
37930
+ return Number(result.changes ?? 0) > 0 ? this.get(id) : void 0;
37931
+ }
37932
+ markDelivered(id, deliveredAt = (/* @__PURE__ */ new Date()).toISOString()) {
37933
+ const result = this.db.prepare(`
37934
+ UPDATE compaction_checkpoints
37935
+ SET delivered_at = ?, delivery_count = delivery_count + 1, updated_at = ?
37936
+ WHERE id = ? AND phase = 'complete' AND status = 'active' AND delivered_at IS NULL
37937
+ `).run(deliveredAt, deliveredAt, id);
37938
+ return Number(result.changes ?? 0) > 0 ? this.get(id) : void 0;
37939
+ }
37940
+ insert(checkpoint) {
37941
+ this.db.prepare(`
37942
+ INSERT INTO compaction_checkpoints (
37943
+ id, project_id, session_id, agent, phase, capture_kind, reason,
37944
+ source_event, source_key, summary, tokens_before, first_kept_entry_id,
37945
+ details_json, transcript_available, status, pre_captured_at, completed_at,
37946
+ delivered_at, delivery_count, created_at, updated_at
37947
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
37948
+ `).run(
37949
+ checkpoint.id,
37950
+ checkpoint.projectId,
37951
+ checkpoint.sessionId,
37952
+ checkpoint.agent,
37953
+ checkpoint.phase,
37954
+ checkpoint.captureKind,
37955
+ checkpoint.reason,
37956
+ checkpoint.sourceEvent,
37957
+ checkpoint.sourceKey,
37958
+ checkpoint.summary ?? null,
37959
+ checkpoint.tokensBefore ?? null,
37960
+ checkpoint.firstKeptEntryId ?? null,
37961
+ JSON.stringify(checkpoint.details ?? {}),
37962
+ checkpoint.transcriptAvailable ? 1 : 0,
37963
+ checkpoint.status,
37964
+ checkpoint.preCapturedAt,
37965
+ checkpoint.completedAt ?? null,
37966
+ checkpoint.deliveredAt ?? null,
37967
+ checkpoint.deliveryCount,
37968
+ checkpoint.createdAt,
37969
+ checkpoint.updatedAt
37970
+ );
37971
+ }
37972
+ };
37973
+ }
37974
+ });
37975
+
37480
37976
  // src/codegraph/auto-context.ts
37481
37977
  var auto_context_exports = {};
37482
37978
  __export(auto_context_exports, {
@@ -37662,6 +38158,22 @@ async function buildAutoProjectContext(input) {
37662
38158
  if (continuationRequested) {
37663
38159
  await initSessionStore(input.dataDir);
37664
38160
  continuation = await getSessionResumeBrief(input.project.id, task, input.reader);
38161
+ const { CompactionCheckpointStore: CompactionCheckpointStore2 } = await Promise.resolve().then(() => (init_compaction_checkpoint_store(), compaction_checkpoint_store_exports));
38162
+ const checkpoint = new CompactionCheckpointStore2(input.dataDir).findLatestCompleted(
38163
+ input.project.id,
38164
+ input.excludeCompactionCheckpointFor ? { excludeSession: input.excludeCompactionCheckpointFor } : void 0
38165
+ );
38166
+ const completedAtMs = checkpoint ? Date.parse(checkpoint.completedAt ?? checkpoint.preCapturedAt) : Number.NaN;
38167
+ if (checkpoint && Number.isFinite(completedAtMs) && completedAtMs <= now3.getTime() && now3.getTime() - completedAtMs <= COMPACT_CHECKPOINT_MAX_AGE_MS) {
38168
+ continuation.compactCheckpoint = {
38169
+ id: checkpoint.id,
38170
+ agent: checkpoint.agent,
38171
+ captureKind: checkpoint.captureKind === "native-summary" ? "native-summary" : "lifecycle",
38172
+ reason: checkpoint.reason,
38173
+ ...checkpoint.completedAt ? { completedAt: checkpoint.completedAt } : {},
38174
+ summary: checkpoint.summary ?? "The host completed context compaction without exposing a native summary. Reconstruct only what the current task needs from current code and durable evidence."
38175
+ };
38176
+ }
37665
38177
  }
37666
38178
  const workset = await buildTaskWorkset({
37667
38179
  projectId: input.project.id,
@@ -37870,7 +38382,7 @@ function formatAutoProjectContextSummary(context) {
37870
38382
  reliableSources.length > 0 ? `- ${reliableSources.length} current code-bound memory link(s)` : "- none yet"
37871
38383
  );
37872
38384
  const continuation = context.workset.continuation;
37873
- if (continuation?.previousSession || continuation?.memories.length) {
38385
+ if (continuation?.previousSession || continuation?.memories.length || continuation?.compactCheckpoint) {
37874
38386
  lines.push("", "Resume from prior work");
37875
38387
  if (continuation.previousSession) {
37876
38388
  const session = continuation.previousSession;
@@ -37885,6 +38397,12 @@ function formatAutoProjectContextSummary(context) {
37885
38397
  "- #" + memory.id + " " + memory.type + ": " + compactContinuationText(memory.title, 18) + detail
37886
38398
  );
37887
38399
  }
38400
+ if (continuation.compactCheckpoint) {
38401
+ const checkpoint = continuation.compactCheckpoint;
38402
+ lines.push(
38403
+ "- Recent host compact checkpoint (" + [checkpoint.agent, checkpoint.captureKind, checkpoint.reason].join(", ") + "): " + compactContinuationText(checkpoint.summary, 36)
38404
+ );
38405
+ }
37888
38406
  }
37889
38407
  return lines.join("\n");
37890
38408
  }
@@ -37951,7 +38469,7 @@ function formatLegacyAutoProjectContextPrompt(context) {
37951
38469
  );
37952
38470
  return lines.join("\n");
37953
38471
  }
37954
- var DEFAULT_MAX_AGE_MS;
38472
+ var DEFAULT_MAX_AGE_MS, COMPACT_CHECKPOINT_MAX_AGE_MS;
37955
38473
  var init_auto_context = __esm({
37956
38474
  "src/codegraph/auto-context.ts"() {
37957
38475
  "use strict";
@@ -37971,6 +38489,7 @@ var init_auto_context = __esm({
37971
38489
  init_session_store();
37972
38490
  init_task_lens();
37973
38491
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
38492
+ COMPACT_CHECKPOINT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
37974
38493
  }
37975
38494
  });
37976
38495
 
@@ -38235,7 +38754,7 @@ function addDefaultVerificationHints(input) {
38235
38754
  return uniq2(hints);
38236
38755
  }
38237
38756
  function selectRelevantObservations(observations2, task, limit) {
38238
- const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
38757
+ const safeLimit2 = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
38239
38758
  const taskTokens2 = tokenize4(task);
38240
38759
  const ranked = observations2.map((observation, index) => ({
38241
38760
  observation,
@@ -38245,7 +38764,7 @@ function selectRelevantObservations(observations2, task, limit) {
38245
38764
  }));
38246
38765
  const positives = ranked.filter((item) => item.score > 0);
38247
38766
  const pool = positives.length > 0 ? positives : ranked;
38248
- return pool.sort((a3, b3) => b3.score - a3.score || b3.time - a3.time || b3.index - a3.index).slice(0, safeLimit).map((item) => item.observation);
38767
+ return pool.sort((a3, b3) => b3.score - a3.score || b3.time - a3.time || b3.index - a3.index).slice(0, safeLimit2).map((item) => item.observation);
38249
38768
  }
38250
38769
  function assembleContextPackForTask(input) {
38251
38770
  const selected = selectRelevantObservations(input.observations, input.task, input.limit ?? 20);
@@ -38642,12 +39161,277 @@ ${formatUsageHint()}`;
38642
39161
  }
38643
39162
  });
38644
39163
 
39164
+ // src/memory/compaction.ts
39165
+ var compaction_exports = {};
39166
+ __export(compaction_exports, {
39167
+ buildCompactionWorkset: () => buildCompactionWorkset,
39168
+ captureCompactionCheckpoint: () => captureCompactionCheckpoint,
39169
+ consumeCompactionWorkset: () => consumeCompactionWorkset
39170
+ });
39171
+ function normalizeBudget(value) {
39172
+ if (!Number.isFinite(value) || value == null) return DEFAULT_WORKSET_BUDGET;
39173
+ return Math.max(MIN_WORKSET_BUDGET, Math.min(2e3, Math.floor(value)));
39174
+ }
39175
+ function sourceLabel(checkpoint) {
39176
+ if (checkpoint.captureKind === "native-summary") return "native host summary";
39177
+ if (checkpoint.captureKind === "preflight") return "pre-compact lifecycle marker";
39178
+ return "host lifecycle marker";
39179
+ }
39180
+ function buildCompactionWorkset(checkpoint, options2 = {}) {
39181
+ const budget = normalizeBudget(options2.maxTokens);
39182
+ const task = options2.task?.trim() ? truncateToTokenBudget(sanitizeCredentials(options2.task.trim()), Math.max(8, Math.floor(budget * 0.25))) : "";
39183
+ const headerLines = [
39184
+ "## Compact Continuation",
39185
+ `- Host: ${checkpoint.agent} (${sourceLabel(checkpoint)})`,
39186
+ `- Trigger: ${checkpoint.reason}`,
39187
+ task ? `- Task now: ${task}` : "",
39188
+ "- Current code remains authoritative."
39189
+ ].filter(Boolean);
39190
+ const header = `${headerLines.join("\n")}
39191
+
39192
+ `;
39193
+ const fallback = checkpoint.captureKind === "native-summary" ? "" : "The host did not expose a native compact summary. Reconstruct only what the current task needs from current code and Memorix evidence.";
39194
+ const source = sanitizeCredentials(checkpoint.summary?.trim() || fallback);
39195
+ const available = Math.max(0, budget - countTextTokens(header) - 4);
39196
+ let excerpt = source ? truncateToTokenBudget(source, available) : "";
39197
+ let text = `${header}${excerpt ? `### Host checkpoint
39198
+ ${excerpt}` : ""}`.trim();
39199
+ while (excerpt && countTextTokens(text) > budget) {
39200
+ const nextExcerptBudget = Math.max(1, Math.floor(countTextTokens(excerpt) * 0.8));
39201
+ excerpt = truncateToTokenBudget(excerpt, nextExcerptBudget);
39202
+ text = `${header}${excerpt ? `### Host checkpoint
39203
+ ${excerpt}` : ""}`.trim();
39204
+ }
39205
+ if (countTextTokens(text) > budget) text = truncateToTokenBudget(text, budget);
39206
+ return {
39207
+ checkpointId: checkpoint.id,
39208
+ text,
39209
+ tokens: countTextTokens(text)
39210
+ };
39211
+ }
39212
+ function sourceEvent(input) {
39213
+ const raw = input.raw;
39214
+ const event = raw.hook_event_name ?? raw.event ?? raw.type;
39215
+ return typeof event === "string" && event ? event : input.event;
39216
+ }
39217
+ async function captureCompactionCheckpoint(input) {
39218
+ const isCompactResume = input.event === "session_start" && input.sessionStartReason?.trim().toLowerCase() === "compact";
39219
+ if (input.event !== "pre_compact" && input.event !== "post_compact" && !isCompactResume) return null;
39220
+ if (!input.sessionId || !input.cwd) return null;
39221
+ const [
39222
+ { detectProject: detectProject2 },
39223
+ { getProjectDataDir: getProjectDataDir2 },
39224
+ { initAliasRegistry: initAliasRegistry2, registerAlias: registerAlias2 }
39225
+ ] = await Promise.all([
39226
+ Promise.resolve().then(() => (init_detector(), detector_exports)),
39227
+ Promise.resolve().then(() => (init_persistence(), persistence_exports)),
39228
+ Promise.resolve().then(() => (init_aliases(), aliases_exports))
39229
+ ]);
39230
+ const project = detectProject2(input.cwd);
39231
+ if (!project) return null;
39232
+ const dataDir = await getProjectDataDir2(project.id);
39233
+ initAliasRegistry2(dataDir);
39234
+ const projectId = await registerAlias2(project);
39235
+ const store2 = new CompactionCheckpointStore(dataDir);
39236
+ if (input.event === "pre_compact") {
39237
+ return store2.recordPreflight({
39238
+ projectId,
39239
+ sessionId: input.sessionId,
39240
+ agent: input.agent,
39241
+ reason: input.compaction?.reason,
39242
+ sourceEvent: sourceEvent(input),
39243
+ transcriptAvailable: Boolean(input.transcriptPath),
39244
+ capturedAt: input.timestamp
39245
+ });
39246
+ }
39247
+ return store2.complete({
39248
+ projectId,
39249
+ sessionId: input.sessionId,
39250
+ agent: input.agent,
39251
+ reason: input.compaction?.reason,
39252
+ sourceEvent: sourceEvent(input),
39253
+ sourceKey: input.compaction?.sourceKey,
39254
+ summary: input.compaction?.summary,
39255
+ tokensBefore: input.compaction?.tokensBefore,
39256
+ firstKeptEntryId: input.compaction?.firstKeptEntryId,
39257
+ details: input.compaction?.details,
39258
+ completedAt: input.timestamp
39259
+ });
39260
+ }
39261
+ async function consumeCompactionWorkset(input, options2 = {}) {
39262
+ if (!input.sessionId || !input.cwd) return null;
39263
+ const [
39264
+ { detectProject: detectProject2 },
39265
+ { getProjectDataDir: getProjectDataDir2 },
39266
+ { initAliasRegistry: initAliasRegistry2, registerAlias: registerAlias2 }
39267
+ ] = await Promise.all([
39268
+ Promise.resolve().then(() => (init_detector(), detector_exports)),
39269
+ Promise.resolve().then(() => (init_persistence(), persistence_exports)),
39270
+ Promise.resolve().then(() => (init_aliases(), aliases_exports))
39271
+ ]);
39272
+ const project = detectProject2(input.cwd);
39273
+ if (!project) return null;
39274
+ const dataDir = await getProjectDataDir2(project.id);
39275
+ initAliasRegistry2(dataDir);
39276
+ const projectId = await registerAlias2(project);
39277
+ const store2 = new CompactionCheckpointStore(dataDir);
39278
+ const checkpoint = store2.findUndelivered(projectId, input.sessionId, input.agent);
39279
+ if (!checkpoint) return null;
39280
+ const workset = buildCompactionWorkset(checkpoint, options2);
39281
+ if (!workset.text) return null;
39282
+ store2.markDelivered(checkpoint.id);
39283
+ return workset;
39284
+ }
39285
+ var DEFAULT_WORKSET_BUDGET, MIN_WORKSET_BUDGET;
39286
+ var init_compaction = __esm({
39287
+ "src/memory/compaction.ts"() {
39288
+ "use strict";
39289
+ init_esm_shims();
39290
+ init_token_budget();
39291
+ init_secret_filter();
39292
+ init_compaction_checkpoint_store();
39293
+ DEFAULT_WORKSET_BUDGET = 420;
39294
+ MIN_WORKSET_BUDGET = 48;
39295
+ }
39296
+ });
39297
+
39298
+ // src/cli/commands/checkpoint.ts
39299
+ var checkpoint_exports = {};
39300
+ __export(checkpoint_exports, {
39301
+ default: () => checkpoint_default
39302
+ });
39303
+ async function getCheckpointContext() {
39304
+ const context = await getCliProjectContext();
39305
+ const { initAliasRegistry: initAliasRegistry2, registerAlias: registerAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
39306
+ initAliasRegistry2(context.dataDir);
39307
+ const canonicalId = await registerAlias2(context.project);
39308
+ return {
39309
+ ...context,
39310
+ project: { ...context.project, id: canonicalId },
39311
+ store: new CompactionCheckpointStore(context.dataDir)
39312
+ };
39313
+ }
39314
+ function assertProjectCheckpoint(checkpoint, projectId, id) {
39315
+ if (!checkpoint || checkpoint.projectId !== projectId) {
39316
+ throw new Error(`No compact checkpoint "${id}" exists for this project.`);
39317
+ }
39318
+ return checkpoint;
39319
+ }
39320
+ function formatCheckpoint(checkpoint) {
39321
+ const completion = checkpoint.completedAt ? `, completed ${checkpoint.completedAt}` : "";
39322
+ const delivery = checkpoint.deliveredAt ? `, delivered ${checkpoint.deliveryCount}x` : "";
39323
+ return [
39324
+ `${shortId(checkpoint.id)} ${checkpoint.phase} ${checkpoint.agent} (${checkpoint.captureKind}, ${checkpoint.reason})`,
39325
+ ` session: ${checkpoint.sessionId}`,
39326
+ ` source: ${checkpoint.sourceEvent}${completion}${delivery}`,
39327
+ ` status: ${checkpoint.status}${checkpoint.transcriptAvailable ? ", transcript marker available" : ""}`,
39328
+ ...checkpoint.summary ? [` summary: ${checkpoint.summary.replace(/\s+/g, " ").slice(0, 220)}`] : []
39329
+ ].join("\n");
39330
+ }
39331
+ function usage() {
39332
+ console.log("Memorix Compact Checkpoints");
39333
+ console.log("");
39334
+ console.log("Usage:");
39335
+ console.log(" memorix checkpoint list [--session <id>] [--agent <name>] [--all]");
39336
+ console.log(" memorix checkpoint show --id <checkpoint-id>");
39337
+ console.log(' memorix checkpoint context [--id <checkpoint-id>] [--task "..."] [--budget 420]');
39338
+ console.log(" memorix checkpoint archive --id <checkpoint-id>");
39339
+ }
39340
+ var checkpoint_default;
39341
+ var init_checkpoint = __esm({
39342
+ "src/cli/commands/checkpoint.ts"() {
39343
+ "use strict";
39344
+ init_esm_shims();
39345
+ init_dist2();
39346
+ init_compaction();
39347
+ init_compaction_checkpoint_store();
39348
+ init_operator_shared();
39349
+ checkpoint_default = defineCommand({
39350
+ meta: {
39351
+ name: "checkpoint",
39352
+ description: "Inspect and manage native compact continuity checkpoints"
39353
+ },
39354
+ args: {
39355
+ id: { type: "string", description: "Checkpoint ID" },
39356
+ session: { type: "string", description: "Filter by host session ID" },
39357
+ agent: { type: "string", description: "Filter by host agent name" },
39358
+ task: { type: "string", description: "Current task for a bounded continuation workset" },
39359
+ budget: { type: "string", description: "Maximum workset token budget (default: 420)" },
39360
+ limit: { type: "string", description: "Maximum checkpoints to list (default: 20)" },
39361
+ all: { type: "boolean", description: "Include archived checkpoints in list output" },
39362
+ json: { type: "boolean", description: "Emit machine-readable JSON output" }
39363
+ },
39364
+ run: async ({ args }) => {
39365
+ const positional = args._ ?? [];
39366
+ const action = (positional[0] ?? "list").toLowerCase();
39367
+ const asJson = Boolean(args.json);
39368
+ try {
39369
+ const { project, store: store2 } = await getCheckpointContext();
39370
+ const id = args.id?.trim();
39371
+ switch (action) {
39372
+ case "list": {
39373
+ const checkpoints = store2.list({
39374
+ projectId: project.id,
39375
+ sessionId: args.session?.trim() || void 0,
39376
+ agent: args.agent?.trim() || void 0,
39377
+ includeArchived: Boolean(args.all),
39378
+ limit: parsePositiveInt(args.limit, 20)
39379
+ });
39380
+ emitResult(
39381
+ { project, checkpoints },
39382
+ checkpoints.length ? checkpoints.map(formatCheckpoint).join("\n\n") : "No compact checkpoints for this project.",
39383
+ asJson
39384
+ );
39385
+ return;
39386
+ }
39387
+ case "show": {
39388
+ if (!id) throw new Error('id is required for "memorix checkpoint show".');
39389
+ const checkpoint = assertProjectCheckpoint(store2.get(id), project.id, id);
39390
+ emitResult({ project, checkpoint }, formatCheckpoint(checkpoint), asJson);
39391
+ return;
39392
+ }
39393
+ case "context": {
39394
+ const checkpoint = id ? assertProjectCheckpoint(store2.get(id), project.id, id) : store2.list({
39395
+ projectId: project.id,
39396
+ sessionId: args.session?.trim() || void 0,
39397
+ agent: args.agent?.trim() || void 0,
39398
+ limit: parsePositiveInt(args.limit, 20)
39399
+ }).find((entry) => entry.phase === "complete");
39400
+ if (!checkpoint) {
39401
+ throw new Error("No completed compact checkpoint is available for this project and filter.");
39402
+ }
39403
+ const workset = buildCompactionWorkset(checkpoint, {
39404
+ task: args.task?.trim(),
39405
+ maxTokens: parsePositiveInt(args.budget, 420)
39406
+ });
39407
+ emitResult({ project, checkpoint, workset }, workset.text, asJson);
39408
+ return;
39409
+ }
39410
+ case "archive": {
39411
+ if (!id) throw new Error('id is required for "memorix checkpoint archive".');
39412
+ assertProjectCheckpoint(store2.get(id), project.id, id);
39413
+ const checkpoint = store2.archive(id);
39414
+ if (!checkpoint) throw new Error(`Compact checkpoint "${id}" is already archived.`);
39415
+ emitResult({ project, checkpoint }, `Archived compact checkpoint ${shortId(checkpoint.id)}.`, asJson);
39416
+ return;
39417
+ }
39418
+ default:
39419
+ usage();
39420
+ }
39421
+ } catch (error2) {
39422
+ emitError(error2 instanceof Error ? error2.message : String(error2), asJson);
39423
+ }
39424
+ }
39425
+ });
39426
+ }
39427
+ });
39428
+
38645
39429
  // src/knowledge/markdown.ts
38646
- import { createHash as createHash10 } from "crypto";
39430
+ import { createHash as createHash11 } from "crypto";
38647
39431
  import { promises as fs16 } from "fs";
38648
39432
  import path23 from "path";
38649
39433
  function hashContent(content) {
38650
- return createHash10("sha256").update(content).digest("hex");
39434
+ return createHash11("sha256").update(content).digest("hex");
38651
39435
  }
38652
39436
  function stringField(data, key) {
38653
39437
  const value = data[key];
@@ -38773,11 +39557,11 @@ __export(wiki_exports, {
38773
39557
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
38774
39558
  readKnowledgePage: () => readKnowledgePage
38775
39559
  });
38776
- import { createHash as createHash11 } from "crypto";
39560
+ import { createHash as createHash12 } from "crypto";
38777
39561
  import { promises as fs17 } from "fs";
38778
39562
  import path24 from "path";
38779
39563
  function hash3(value) {
38780
- return createHash11("sha256").update(value).digest("hex");
39564
+ return createHash12("sha256").update(value).digest("hex");
38781
39565
  }
38782
39566
  function now2() {
38783
39567
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -39218,7 +40002,7 @@ function modeFrom(value) {
39218
40002
  if (value === "versioned") return "versioned";
39219
40003
  throw new Error("knowledge mode must be local or versioned");
39220
40004
  }
39221
- function usage() {
40005
+ function usage2() {
39222
40006
  return [
39223
40007
  "Memorix Knowledge Commands",
39224
40008
  "",
@@ -39439,7 +40223,7 @@ var init_knowledge = __esm({
39439
40223
  );
39440
40224
  return;
39441
40225
  }
39442
- emitResult({ usage: usage() }, usage(), asJson);
40226
+ emitResult({ usage: usage2() }, usage2(), asJson);
39443
40227
  return;
39444
40228
  }
39445
40229
  switch (action) {
@@ -39498,7 +40282,7 @@ var init_knowledge = __esm({
39498
40282
  return;
39499
40283
  }
39500
40284
  default:
39501
- emitResult({ usage: usage() }, usage(), asJson);
40285
+ emitResult({ usage: usage2() }, usage2(), asJson);
39502
40286
  }
39503
40287
  } catch (error2) {
39504
40288
  emitError(error2 instanceof Error ? error2.message : String(error2), asJson);
@@ -46812,7 +47596,7 @@ __export(planner_exports, {
46812
47596
  materializeTaskGraph: () => materializeTaskGraph,
46813
47597
  seedAutonomousPipeline: () => seedAutonomousPipeline
46814
47598
  });
46815
- import { randomUUID as randomUUID9 } from "crypto";
47599
+ import { randomUUID as randomUUID10 } from "crypto";
46816
47600
  function isPlannerTask(metadata) {
46817
47601
  if (!metadata) return null;
46818
47602
  try {
@@ -46870,7 +47654,7 @@ function seedAutonomousPipeline(teamStore, projectId, config2, opts) {
46870
47654
  const maxIterations = config2.maxIterations ?? 3;
46871
47655
  const taskBudget = config2.taskBudget ?? 15;
46872
47656
  const structuredPlan = opts?.structuredPlan ?? true;
46873
- const pipelineId = randomUUID9();
47657
+ const pipelineId = randomUUID10();
46874
47658
  const meta = {
46875
47659
  plannerType: "plan",
46876
47660
  pipelineId,
@@ -47789,11 +48573,11 @@ __export(receipt_service_exports, {
47789
48573
  buildHandoffReceipt: () => buildHandoffReceipt,
47790
48574
  formatHandoffReceipt: () => formatHandoffReceipt
47791
48575
  });
47792
- import { createHash as createHash12 } from "crypto";
48576
+ import { createHash as createHash13 } from "crypto";
47793
48577
  import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
47794
48578
  import path26 from "path";
47795
48579
  function sha256(value) {
47796
- return `sha256:${createHash12("sha256").update(value).digest("hex")}`;
48580
+ return `sha256:${createHash13("sha256").update(value).digest("hex")}`;
47797
48581
  }
47798
48582
  function hashId(projectId, id) {
47799
48583
  return sha256(`${projectId}:obs:${id}`);
@@ -48039,6 +48823,7 @@ var init_tool_profile = __esm({
48039
48823
  memorix_rules_sync: ["full"],
48040
48824
  memorix_workspace_sync: ["full"],
48041
48825
  memorix_ingest_image: ["full"],
48826
+ memorix_compaction_checkpoint: ["full"],
48042
48827
  // ── MCP Official Memory Server compatibility (KG tools) ──────────
48043
48828
  // These are only useful to users specifically migrating from the
48044
48829
  // reference mcp-memory server. Hide them unless explicitly enabled.
@@ -66294,9 +67079,9 @@ var init_graph_scope = __esm({
66294
67079
  });
66295
67080
 
66296
67081
  // src/rules/utils.ts
66297
- import { createHash as createHash13 } from "crypto";
67082
+ import { createHash as createHash14 } from "crypto";
66298
67083
  function hashContent2(content) {
66299
- return createHash13("sha256").update(content.trim()).digest("hex").substring(0, 16);
67084
+ return createHash14("sha256").update(content.trim()).digest("hex").substring(0, 16);
66300
67085
  }
66301
67086
  function generateRuleId(source, filePath) {
66302
67087
  const sanitized = filePath.replace(/[\/\\]/g, "-").replace(/^\./, "");
@@ -67875,6 +68660,26 @@ function vectorBatchSize(payload) {
67875
68660
  if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_VECTOR_BATCH_SIZE;
67876
68661
  return Math.min(100, Math.max(1, Math.floor(value)));
67877
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
+ }
67878
68683
  function retentionBatchSize(payload) {
67879
68684
  const value = payload.limit;
67880
68685
  if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RETENTION_BATCH_SIZE;
@@ -68283,20 +69088,45 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
68283
69088
  if (isEmbeddingExplicitlyDisabled2()) return { action: "complete" };
68284
69089
  const { backfillVectorEmbeddings: backfillVectorEmbeddings2, getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
68285
69090
  const before = getVectorStatus2(projectId);
68286
- if (before.missing === 0) return { action: "complete" };
69091
+ if (before.missing === 0) {
69092
+ resolveSupersededVectorFailures(projectDir2, projectId);
69093
+ return { action: "complete" };
69094
+ }
68287
69095
  const result = await backfillVectorEmbeddings2({
68288
69096
  projectId,
68289
69097
  limit: vectorBatchSize(job.payload)
68290
69098
  });
68291
69099
  const after = getVectorStatus2(projectId);
68292
- if (after.missing === 0) return { action: "complete" };
69100
+ if (after.missing === 0) {
69101
+ resolveSupersededVectorFailures(projectDir2, projectId);
69102
+ return { action: "complete" };
69103
+ }
68293
69104
  if (result.failed > 0 && result.succeeded === 0) {
68294
- throw new Error(`vector backfill made no progress (${result.failed}/${result.attempted} failed)`);
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
+ };
68295
69119
  }
69120
+ const priorFailureStreak = vectorBackfillFailureStreak(job.payload);
69121
+ const payload = withoutVectorBackfillFailureStreak(job.payload);
68296
69122
  return {
68297
69123
  action: "reschedule",
68298
69124
  delayMs: result.attempted === 0 ? VECTOR_RETRY_DELAY_MS : 0,
68299
- 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
+ } : {}
68300
69130
  };
68301
69131
  };
68302
69132
  }
@@ -68309,11 +69139,12 @@ function createProjectMaintenanceDispatcher(projectId, projectDir2, projectRoot,
68309
69139
  return isolatedRunner({ job, projectRoot, dataDir: projectDir2 });
68310
69140
  };
68311
69141
  }
68312
- 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;
68313
69143
  var init_project_maintenance = __esm({
68314
69144
  "src/runtime/project-maintenance.ts"() {
68315
69145
  "use strict";
68316
69146
  init_esm_shims();
69147
+ init_maintenance_jobs();
68317
69148
  init_isolated_maintenance();
68318
69149
  init_lifecycle();
68319
69150
  init_timeout();
@@ -68324,6 +69155,8 @@ var init_project_maintenance = __esm({
68324
69155
  DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
68325
69156
  DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
68326
69157
  VECTOR_RETRY_DELAY_MS = 5e3;
69158
+ VECTOR_FAILURE_BASE_RETRY_DELAY_MS = 6e4;
69159
+ VECTOR_FAILURE_MAX_RETRY_DELAY_MS = 15 * 6e4;
68327
69160
  DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
68328
69161
  }
68329
69162
  });
@@ -69206,6 +70039,17 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
69206
70039
  sourceCounts,
69207
70040
  recentObservations: sorted,
69208
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
+ },
69209
70053
  storage: storageInfo,
69210
70054
  maintenance,
69211
70055
  ...lifecycle ? { lifecycle } : {},
@@ -70075,7 +70919,7 @@ __export(server_exports2, {
70075
70919
  createMemorixServer: () => createMemorixServer,
70076
70920
  shouldAwaitProjectRuntime: () => shouldAwaitProjectRuntime
70077
70921
  });
70078
- import { createHash as createHash14 } from "crypto";
70922
+ import { createHash as createHash15 } from "crypto";
70079
70923
  function formatFormationStageDurations(stageDurationsMs) {
70080
70924
  const orderedStages = ["extract", "resolve", "evaluate"];
70081
70925
  const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
@@ -70157,7 +71001,7 @@ function coerceObjectArray(val) {
70157
71001
  return [];
70158
71002
  }
70159
71003
  function createDeterministicInstanceId(projectId, agentType, agentName) {
70160
- const digest2 = createHash14("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
71004
+ const digest2 = createHash15("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
70161
71005
  return `auto-${digest2}`;
70162
71006
  }
70163
71007
  function shouldAwaitProjectRuntime(toolName) {
@@ -70430,7 +71274,7 @@ The path should point to a directory containing a .git folder.`
70430
71274
  };
70431
71275
  const server = existingServer ?? new McpServer({
70432
71276
  name: "memorix",
70433
- version: true ? "1.2.6" : "1.0.1"
71277
+ version: true ? "1.2.8" : "1.0.1"
70434
71278
  });
70435
71279
  const originalRegisterTool = server.registerTool.bind(server);
70436
71280
  server.registerTool = ((name, ...args) => {
@@ -71078,12 +71922,12 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
71078
71922
  if (boundary) return boundary;
71079
71923
  }
71080
71924
  return withFreshIndex(async () => {
71081
- const safeLimit = limit != null ? coerceNumber(limit, 20) : void 0;
71925
+ const safeLimit2 = limit != null ? coerceNumber(limit, 20) : void 0;
71082
71926
  const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
71083
71927
  const TIMEOUT_MS = 3e4;
71084
71928
  const searchPromise = compactSearch({
71085
71929
  query,
71086
- limit: safeLimit,
71930
+ limit: safeLimit2,
71087
71931
  type,
71088
71932
  maxTokens: safeMaxTokens,
71089
71933
  since,
@@ -71884,10 +72728,10 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
71884
72728
  const unresolved = requireResolvedProject("search reasoning in the current project");
71885
72729
  if (unresolved) return unresolved;
71886
72730
  }
71887
- const safeLimit = limit != null ? coerceNumber(limit, 10) : 10;
72731
+ const safeLimit2 = limit != null ? coerceNumber(limit, 10) : 10;
71888
72732
  const result = await withFreshIndex(() => compactSearch({
71889
72733
  query,
71890
- limit: safeLimit,
72734
+ limit: safeLimit2,
71891
72735
  type: "reasoning",
71892
72736
  projectId: scope === "global" ? void 0 : project.id,
71893
72737
  status: "active",
@@ -73210,9 +74054,9 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
73210
74054
  }
73211
74055
  },
73212
74056
  async ({ limit }) => {
73213
- const safeLimit = limit != null ? coerceNumber(limit, 3) : 3;
74057
+ const safeLimit2 = limit != null ? coerceNumber(limit, 3) : 3;
73214
74058
  const { getSessionContext: getSessionContext2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session(), session_exports));
73215
- const context = await getSessionContext2(projectDir2, project.id, safeLimit, getObservationReader());
74059
+ const context = await getSessionContext2(projectDir2, project.id, safeLimit2, getObservationReader());
73216
74060
  const sessions = await listSessions2(projectDir2, project.id);
73217
74061
  const activeSessions = sessions.filter((s2) => s2.status === "active");
73218
74062
  const completedSessions = sessions.filter((s2) => s2.status === "completed");
@@ -73233,6 +74077,86 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
73233
74077
  };
73234
74078
  }
73235
74079
  );
74080
+ server.registerTool(
74081
+ "memorix_compaction_checkpoint",
74082
+ {
74083
+ title: "Compact Continuity Checkpoints",
74084
+ description: "Inspect, preview, or archive bounded checkpoints recorded around a host-native context compaction. Use only to debug or explicitly review compaction continuity. These are lifecycle records, not durable memories or transcript backups. CLI equivalent: memorix checkpoint list|show|context|archive.",
74085
+ inputSchema: {
74086
+ action: external_exports.enum(["list", "show", "context", "archive"]).default("list"),
74087
+ id: external_exports.string().optional().describe("Checkpoint ID for show, context, or archive"),
74088
+ sessionId: external_exports.string().optional().describe("Optional host session ID filter"),
74089
+ agent: external_exports.string().optional().describe("Optional host agent filter"),
74090
+ task: external_exports.string().optional().describe("Current task for a bounded context preview"),
74091
+ maxTokens: external_exports.number().optional().describe("Maximum tokens for action=context (default: 420)"),
74092
+ limit: external_exports.number().optional().describe("Maximum records for action=list (default: 20)"),
74093
+ includeArchived: external_exports.boolean().optional().default(false).describe("Include archived records in action=list")
74094
+ }
74095
+ },
74096
+ async ({ action, id, sessionId, agent, task, maxTokens, limit, includeArchived }) => {
74097
+ const unresolved = requireResolvedProject("inspect compact continuity checkpoints");
74098
+ if (unresolved) return unresolved;
74099
+ const [{ CompactionCheckpointStore: CompactionCheckpointStore2 }, { buildCompactionWorkset: buildCompactionWorkset2 }] = await Promise.all([
74100
+ Promise.resolve().then(() => (init_compaction_checkpoint_store(), compaction_checkpoint_store_exports)),
74101
+ Promise.resolve().then(() => (init_compaction(), compaction_exports))
74102
+ ]);
74103
+ const store2 = new CompactionCheckpointStore2(projectDir2);
74104
+ const assertCheckpoint = (checkpointId) => {
74105
+ const checkpoint2 = store2.get(checkpointId);
74106
+ if (!checkpoint2 || checkpoint2.projectId !== project.id) return null;
74107
+ return checkpoint2;
74108
+ };
74109
+ if (action === "list") {
74110
+ const checkpoints = store2.list({
74111
+ projectId: project.id,
74112
+ sessionId: sessionId?.trim() || void 0,
74113
+ agent: agent?.trim() || void 0,
74114
+ includeArchived: Boolean(includeArchived),
74115
+ limit: limit != null ? Math.max(1, Math.min(100, coerceNumber(limit, 20))) : 20
74116
+ });
74117
+ return {
74118
+ content: [{ type: "text", text: JSON.stringify({ projectId: project.id, checkpoints }, null, 2) }]
74119
+ };
74120
+ }
74121
+ if (!id?.trim()) {
74122
+ return {
74123
+ content: [{ type: "text", text: "id is required for this checkpoint action." }],
74124
+ isError: true
74125
+ };
74126
+ }
74127
+ const checkpoint = assertCheckpoint(id.trim());
74128
+ if (!checkpoint) {
74129
+ return {
74130
+ content: [{ type: "text", text: `Checkpoint "${id.trim()}" was not found for the current project.` }],
74131
+ isError: true
74132
+ };
74133
+ }
74134
+ if (action === "show") {
74135
+ return {
74136
+ content: [{ type: "text", text: JSON.stringify({ projectId: project.id, checkpoint }, null, 2) }]
74137
+ };
74138
+ }
74139
+ if (action === "context") {
74140
+ const workset = buildCompactionWorkset2(checkpoint, {
74141
+ task: task?.trim(),
74142
+ maxTokens: maxTokens != null ? coerceNumber(maxTokens, 420) : 420
74143
+ });
74144
+ return {
74145
+ content: [{ type: "text", text: workset.text }]
74146
+ };
74147
+ }
74148
+ const archived = store2.archive(checkpoint.id);
74149
+ if (!archived) {
74150
+ return {
74151
+ content: [{ type: "text", text: `Checkpoint "${checkpoint.id}" is already archived.` }],
74152
+ isError: true
74153
+ };
74154
+ }
74155
+ return {
74156
+ content: [{ type: "text", text: `Archived compact checkpoint ${archived.id}.` }]
74157
+ };
74158
+ }
74159
+ );
73236
74160
  server.registerTool(
73237
74161
  "memorix_transfer",
73238
74162
  {
@@ -76147,7 +77071,7 @@ var init_serve_http = __esm({
76147
77071
  },
76148
77072
  run: async ({ args }) => {
76149
77073
  const { createServer: createServer2 } = await import("http");
76150
- const { randomUUID: randomUUID10 } = await import("crypto");
77074
+ const { randomUUID: randomUUID11 } = await import("crypto");
76151
77075
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp(), streamableHttp_exports));
76152
77076
  const { isInitializeRequest: isInitializeRequest2 } = await Promise.resolve().then(() => (init_types4(), types_exports));
76153
77077
  const { createMemorixServer: createMemorixServer2 } = await Promise.resolve().then(() => (init_server4(), server_exports2));
@@ -76331,7 +77255,7 @@ var init_serve_http = __esm({
76331
77255
  if (!sessionId && isInitializeRequest2(body)) {
76332
77256
  let createdState = null;
76333
77257
  const transport = new StreamableHTTPServerTransport2({
76334
- sessionIdGenerator: () => randomUUID10(),
77258
+ sessionIdGenerator: () => randomUUID11(),
76335
77259
  enableJsonResponse: true,
76336
77260
  onsessioninitialized: (sid) => {
76337
77261
  if (createdState) sessions.set(sid, createdState);
@@ -76726,10 +77650,18 @@ var init_serve_http = __esm({
76726
77650
  };
76727
77651
  } catch {
76728
77652
  }
76729
- let vectorStatus = { total: 0, missing: 0, missingIds: [], backfillRunning: false };
77653
+ let vectorStatus = {
77654
+ available: false,
77655
+ total: 0,
77656
+ missing: 0,
77657
+ missingIds: [],
77658
+ backfillRunning: false
77659
+ };
76730
77660
  try {
76731
- const { getVectorStatus: getVectorStatus2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
76732
- vectorStatus = getVectorStatus2(statsProjectId);
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
+ }
76733
77665
  } catch {
76734
77666
  }
76735
77667
  let searchMode = "fulltext";
@@ -78121,6 +79053,44 @@ function stringifyValue(value) {
78121
79053
  return String(value);
78122
79054
  }
78123
79055
  }
79056
+ function firstNumber2(...values) {
79057
+ for (const value of values) {
79058
+ if (typeof value === "number" && Number.isFinite(value)) return value;
79059
+ if (typeof value === "string" && value.trim()) {
79060
+ const parsed = Number(value);
79061
+ if (Number.isFinite(parsed)) return parsed;
79062
+ }
79063
+ }
79064
+ return void 0;
79065
+ }
79066
+ function normalizeCompactionReason(value) {
79067
+ const normalized = value?.trim().toLowerCase();
79068
+ if (normalized === "manual" || normalized === "auto") return normalized;
79069
+ return "unknown";
79070
+ }
79071
+ function normalizeCompactionMetadata(payload, ...records) {
79072
+ const nestedSources = records.filter((record2) => Boolean(record2));
79073
+ const sources = [payload, ...nestedSources];
79074
+ const from = (field) => sources.map((source) => source[field]);
79075
+ const fromNested = (field) => nestedSources.map((source) => source[field]);
79076
+ const summary = firstString(...from("summary"), ...from("compaction_summary"));
79077
+ const sourceKey = firstString(...from("compaction_id"), ...from("entry_id"), ...fromNested("id"));
79078
+ const reasonValue = firstString(...from("trigger"), ...from("reason"), ...from("compaction_reason"));
79079
+ const tokensBefore = firstNumber2(...from("tokensBefore"), ...from("tokens_before"));
79080
+ const firstKeptEntryId = firstString(...from("firstKeptEntryId"), ...from("first_kept_entry_id"));
79081
+ const details = sources.map((source) => asRecord(source.details)).find((value) => Boolean(value));
79082
+ if (!summary && !sourceKey && !tokensBefore && !firstKeptEntryId && !details && !reasonValue) {
79083
+ return void 0;
79084
+ }
79085
+ return {
79086
+ ...reasonValue ? { reason: normalizeCompactionReason(reasonValue) } : {},
79087
+ ...sourceKey ? { sourceKey } : {},
79088
+ ...summary ? { summary } : {},
79089
+ ...tokensBefore !== void 0 ? { tokensBefore } : {},
79090
+ ...firstKeptEntryId ? { firstKeptEntryId } : {},
79091
+ ...details ? { details } : {}
79092
+ };
79093
+ }
78124
79094
  function detectAgent(payload) {
78125
79095
  if (typeof payload._memorix_agent === "string") {
78126
79096
  return payload._memorix_agent;
@@ -78206,6 +79176,10 @@ function normalizeClaude(payload, event) {
78206
79176
  if (assistantMessage) {
78207
79177
  result.aiResponse = assistantMessage;
78208
79178
  }
79179
+ if (event === "session_start") {
79180
+ const source = firstString(payload.source, payload.session_start_reason, payload.reason);
79181
+ if (source) result.sessionStartReason = source;
79182
+ }
78209
79183
  return result;
78210
79184
  }
78211
79185
  function normalizeWindsurf(payload, event) {
@@ -78344,6 +79318,10 @@ function normalizeGemini(payload, event) {
78344
79318
  if (event === "user_prompt") {
78345
79319
  result.userPrompt = payload.prompt ?? "";
78346
79320
  }
79321
+ if (event === "session_start") {
79322
+ const source = firstString(payload.source, payload.session_start_reason, payload.reason);
79323
+ if (source) result.sessionStartReason = source;
79324
+ }
78347
79325
  return result;
78348
79326
  }
78349
79327
  function normalizeOpenCode(payload, event) {
@@ -78380,7 +79358,8 @@ function normalizeOpenCode(payload, event) {
78380
79358
  function normalizePi(payload, event) {
78381
79359
  const result = {
78382
79360
  sessionId: payload.session_id ?? payload.sessionId ?? "",
78383
- cwd: payload.cwd ?? ""
79361
+ cwd: payload.cwd ?? "",
79362
+ transcriptPath: payload.transcript_path ?? payload.transcriptPath
78384
79363
  };
78385
79364
  const toolName = payload.tool_name ?? "";
78386
79365
  if (toolName) {
@@ -78402,6 +79381,10 @@ function normalizePi(payload, event) {
78402
79381
  if (event === "post_command") {
78403
79382
  result.command = payload.command ?? "";
78404
79383
  }
79384
+ if (event === "session_start") {
79385
+ const source = firstString(payload.source, payload.reason);
79386
+ if (source) result.sessionStartReason = source;
79387
+ }
78405
79388
  if (result.toolInput && typeof result.toolInput === "object") {
78406
79389
  const filePath = result.toolInput.file_path ?? result.toolInput.filePath ?? result.toolInput.path;
78407
79390
  if (filePath) result.filePath = filePath;
@@ -78454,6 +79437,10 @@ function normalizeBridgePayload(payload, event) {
78454
79437
  openclawContext?.message
78455
79438
  ) ?? stringifyValue(openclawEvent?.message) ?? "";
78456
79439
  }
79440
+ if (event === "session_start") {
79441
+ const source = firstString(payload.source, bridgePayload?.source, bridgeKwargs?.source, openclawEvent?.reason);
79442
+ if (source) result.sessionStartReason = source;
79443
+ }
78457
79444
  return result;
78458
79445
  }
78459
79446
  function normalizeHookInput(payload) {
@@ -78499,6 +79486,15 @@ function normalizeHookInput(payload) {
78499
79486
  default:
78500
79487
  agentSpecific = { sessionId: "", cwd: "" };
78501
79488
  }
79489
+ const openclawEvent = asRecord(payload.openclaw_event);
79490
+ const genericCompaction = event === "pre_compact" || event === "post_compact" ? normalizeCompactionMetadata(
79491
+ payload,
79492
+ asRecord(payload.compaction),
79493
+ asRecord(payload.compaction_entry),
79494
+ asRecord(payload.compactionEntry),
79495
+ asRecord(openclawEvent?.compaction),
79496
+ asRecord(openclawEvent?.compaction_entry)
79497
+ ) : void 0;
78502
79498
  return {
78503
79499
  event,
78504
79500
  agent,
@@ -78506,7 +79502,8 @@ function normalizeHookInput(payload) {
78506
79502
  sessionId: agentSpecific.sessionId ?? "",
78507
79503
  cwd: agentSpecific.cwd ?? "",
78508
79504
  raw: payload,
78509
- ...agentSpecific
79505
+ ...agentSpecific,
79506
+ ...genericCompaction ? { compaction: genericCompaction } : {}
78510
79507
  };
78511
79508
  }
78512
79509
  var EVENT_MAP;
@@ -78760,10 +79757,10 @@ __export(handler_exports, {
78760
79757
  resetCooldowns: () => resetCooldowns,
78761
79758
  runHook: () => runHook
78762
79759
  });
78763
- import { createHash as createHash15 } from "crypto";
79760
+ import { createHash as createHash16 } from "crypto";
78764
79761
  function deriveHookActorId(input) {
78765
79762
  const material = `${input.agent ?? "unknown"}\0${input.sessionId ?? "unknown"}`;
78766
- return `hook:${createHash15("sha256").update(material).digest("hex").slice(0, 24)}`;
79763
+ return `hook:${createHash16("sha256").update(material).digest("hex").slice(0, 24)}`;
78767
79764
  }
78768
79765
  function classifyTool(input) {
78769
79766
  if (input.event === "post_edit") return "file_modify";
@@ -78944,6 +79941,15 @@ async function buildHookProjectContext(input, task, target) {
78944
79941
  refresh: "auto",
78945
79942
  reader: { projectId: canonicalId },
78946
79943
  continuation: "always",
79944
+ ...input.sessionId ? {
79945
+ // A native compact recovery is delivered once per host session.
79946
+ // Do not echo that same checkpoint back through the generic
79947
+ // continuation brief on later events from this session.
79948
+ excludeCompactionCheckpointFor: {
79949
+ sessionId: input.sessionId,
79950
+ agent: input.agent
79951
+ }
79952
+ } : {},
78947
79953
  enqueueRefresh: () => Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports)).then(({ enqueueCodegraphRefresh: enqueueCodegraphRefresh2 }) => {
78948
79954
  enqueueCodegraphRefresh2({
78949
79955
  dataDir,
@@ -78965,27 +79971,80 @@ async function buildHookProjectContext(input, task, target) {
78965
79971
  return null;
78966
79972
  }
78967
79973
  }
79974
+ function isCompactSessionStart(input) {
79975
+ return input.event === "session_start" && input.sessionStartReason?.trim().toLowerCase() === "compact";
79976
+ }
79977
+ async function consumeCompactContinuation(input, task) {
79978
+ try {
79979
+ const { consumeCompactionWorkset: consumeCompactionWorkset2 } = await Promise.resolve().then(() => (init_compaction(), compaction_exports));
79980
+ const workset = await consumeCompactionWorkset2(input, { task, maxTokens: 420 });
79981
+ return workset?.text;
79982
+ } catch (error2) {
79983
+ console.error("[memorix] compact continuation failed:", error2?.message ?? error2);
79984
+ return void 0;
79985
+ }
79986
+ }
78968
79987
  async function buildClaudeContinuationPromptContext(input) {
78969
79988
  if (input.agent !== "claude" || input.event !== "user_prompt" || !input.userPrompt?.trim()) {
78970
79989
  return void 0;
78971
79990
  }
78972
- const { isContinuationTask: isContinuationTask2 } = await Promise.resolve().then(() => (init_task_lens(), task_lens_exports));
78973
- if (!isContinuationTask2(input.userPrompt)) return void 0;
78974
79991
  if (await getHookInjectionMode(input) === "silent") return void 0;
78975
- const context = await buildHookProjectContext(input, input.userPrompt, "hook-user-prompt");
78976
- if (!context?.hasContinuation) return void 0;
78977
- return [
78978
- "Memorix prepared a bounded prior-work brief for this explicit continuation request.",
78979
- "Treat it as background context; current code wins. Use it before broad Git or file-history archaeology.",
78980
- "",
78981
- context.prompt
78982
- ].join("\n");
79992
+ const compactContinuation = await consumeCompactContinuation(input, input.userPrompt);
79993
+ if (compactContinuation) {
79994
+ return [
79995
+ "Memorix recovered one bounded checkpoint after the host compacted this session.",
79996
+ "Treat it as background context; current code and the user request win.",
79997
+ "",
79998
+ compactContinuation
79999
+ ].join("\n");
80000
+ }
80001
+ const { isContinuationTask: isContinuationTask2, resolveTaskLens: resolveTaskLens2 } = await Promise.resolve().then(() => (init_task_lens(), task_lens_exports));
80002
+ const continuationRequested = isContinuationTask2(input.userPrompt);
80003
+ if (continuationRequested) {
80004
+ const context = await buildHookProjectContext(input, input.userPrompt, "hook-user-prompt");
80005
+ if (context?.hasContinuation) {
80006
+ return [
80007
+ "Memorix prepared a bounded prior-work brief for this explicit continuation request.",
80008
+ "Treat it as background context; current code wins. Use it before broad Git or file-history archaeology.",
80009
+ "",
80010
+ context.prompt
80011
+ ].join("\n");
80012
+ }
80013
+ }
80014
+ if (continuationRequested || resolveTaskLens2(input.userPrompt).id === "onboarding") {
80015
+ return [
80016
+ "Memorix is available for this handoff or continuation.",
80017
+ "Before broad file or Git exploration, call memorix_project_context with the user's actual task.",
80018
+ "If it is not visible yet, use Claude Code tool search for memorix_project_context; use CLI only when MCP discovery is unavailable."
80019
+ ].join(" ");
80020
+ }
80021
+ return void 0;
78983
80022
  }
78984
80023
  async function handleSessionStart(input) {
78985
80024
  const injectMode = await getHookInjectionMode(input);
78986
80025
  if (injectMode === "silent") {
78987
80026
  return { observation: null, output: { continue: true } };
78988
80027
  }
80028
+ if (input.agent === "codex" && isCompactSessionStart(input)) {
80029
+ const compactContinuation = await consumeCompactContinuation(
80030
+ input,
80031
+ "Continue after the host compacted the current session."
80032
+ );
80033
+ if (compactContinuation) {
80034
+ return {
80035
+ observation: null,
80036
+ output: {
80037
+ continue: true,
80038
+ systemMessage: [
80039
+ "Memorix recovered one bounded checkpoint after the host compacted this session.",
80040
+ "Treat it as background context; current code and the user request win.",
80041
+ "",
80042
+ compactContinuation
80043
+ ].join("\n")
80044
+ }
80045
+ };
80046
+ }
80047
+ }
78989
80048
  let contextSummary = "";
78990
80049
  if (injectMode === "full") {
78991
80050
  const context = await buildHookProjectContext(input, "Continue the current task.", "hook-session-start");
@@ -79194,6 +80253,12 @@ async function runHook(agentOverride, eventOverride) {
79194
80253
  payload._memorix_event = eventOverride;
79195
80254
  }
79196
80255
  const input = normalizeHookInput(payload);
80256
+ try {
80257
+ const { captureCompactionCheckpoint: captureCompactionCheckpoint2 } = await Promise.resolve().then(() => (init_compaction(), compaction_exports));
80258
+ await captureCompactionCheckpoint2(input);
80259
+ } catch (checkpointError) {
80260
+ console.error("[memorix] compact checkpoint failed:", checkpointError?.message ?? checkpointError);
80261
+ }
79197
80262
  const { observation, output } = await handleHookEvent(input, { deferMaintenance: true });
79198
80263
  if (observation) {
79199
80264
  try {
@@ -81341,6 +82406,9 @@ function asRecordArray(value) {
81341
82406
  function codexPluginPath() {
81342
82407
  return `${homedir32()}/.codex/plugins/${CODEX_PLUGIN_NAME}`;
81343
82408
  }
82409
+ function codexPluginMcpPath() {
82410
+ return `${codexPluginPath()}/.mcp.json`;
82411
+ }
81344
82412
  function codexMarketplacePath() {
81345
82413
  return `${homedir32()}/.agents/plugins/marketplace.json`;
81346
82414
  }
@@ -81373,6 +82441,7 @@ async function inspectCodexPluginBundle() {
81373
82441
  const pluginPath = codexPluginPath();
81374
82442
  const manifestPath = `${pluginPath}/.codex-plugin/plugin.json`;
81375
82443
  const hooksPath = `${pluginPath}/hooks/hooks.json`;
82444
+ const mcpPath = codexPluginMcpPath();
81376
82445
  if (!existsSync25(pluginPath)) {
81377
82446
  return {
81378
82447
  scope: "global",
@@ -81420,6 +82489,19 @@ async function inspectCodexPluginBundle() {
81420
82489
  const issues = [];
81421
82490
  if (version2 !== getCliVersion()) issues.push("codex-plugin-version-mismatch");
81422
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
+ }
81423
82505
  let declared = [];
81424
82506
  if (!existsSync25(hooksPath)) {
81425
82507
  issues.push("codex-hook-manifest-missing");
@@ -81569,7 +82651,9 @@ async function inspectCodexHookTrust() {
81569
82651
  kind: "hook-trust",
81570
82652
  path: configPath,
81571
82653
  exists: true,
81572
- status: issues.length > 0 ? "repairable" : "ok",
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",
81573
82657
  issues,
81574
82658
  hookTrust: { trusted, expected: [...CODEX_HOOK_STATE_NAMES] }
81575
82659
  };
@@ -81637,7 +82721,7 @@ function defaultClaudeProjectKey(projectRoot) {
81637
82721
  function getClaudeLocalConfigPath() {
81638
82722
  return `${homedir32()}/.claude.json`;
81639
82723
  }
81640
- function coerceClaudeLocalServer(name, value) {
82724
+ function coerceMcpServer(name, value) {
81641
82725
  const entry = asRecord2(value);
81642
82726
  if (!entry) return null;
81643
82727
  const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
@@ -81696,7 +82780,7 @@ async function inspectClaudeLocalMcp(projectRoot) {
81696
82780
  };
81697
82781
  }
81698
82782
  const servers = asRecord2(localProject.project.mcpServers);
81699
- const server = coerceClaudeLocalServer("memorix", servers?.memorix);
82783
+ const server = coerceMcpServer("memorix", servers?.memorix);
81700
82784
  if (!server) {
81701
82785
  return {
81702
82786
  scope: "local",
@@ -81747,6 +82831,56 @@ async function installClaudeLocalMcpConfig(projectRoot) {
81747
82831
  await writeFile7(configPath, `${JSON.stringify(config2, null, 2)}
81748
82832
  `, "utf-8");
81749
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
+ }
81750
82884
  async function inspectMcp(agent, projectRoot, scope) {
81751
82885
  if (!isMcpConfigAgent(agent)) {
81752
82886
  return { status: "skipped", issues: ["mcp-managed-by-package"], checks: [] };
@@ -81758,6 +82892,13 @@ async function inspectMcp(agent, projectRoot, scope) {
81758
82892
  checks.push(await inspectClaudeLocalMcp(projectRoot));
81759
82893
  continue;
81760
82894
  }
82895
+ if (agent === "codex" && targetScope === "global") {
82896
+ const pluginCheck = await inspectCodexPluginMcp();
82897
+ if (pluginCheck) {
82898
+ checks.push(pluginCheck);
82899
+ continue;
82900
+ }
82901
+ }
81761
82902
  const configPath = adapter.getConfigPath(targetScope === "project" ? projectRoot : void 0);
81762
82903
  if (targetScope === "global" && configPath === adapter.getConfigPath(projectRoot)) continue;
81763
82904
  if (!existsSync25(configPath)) {
@@ -81902,10 +83043,19 @@ function formatAgentIntegrationReport(report) {
81902
83043
  lines.push(`${entry.agent}: ${status}`);
81903
83044
  if (entry.mcp.issues.length > 0) lines.push(` MCP: ${entry.mcp.issues.join(", ")}`);
81904
83045
  if (entry.guidance.issues.length > 0) lines.push(` Guidance: ${entry.guidance.issues.join(", ")}`);
81905
- if (entry.plugin.issues.length > 0) lines.push(` Plugin: ${entry.plugin.issues.join(", ")}`);
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
+ }
81906
83052
  }
81907
83053
  lines.push("");
81908
- lines.push(`Repair: ${report.repairCommand}`);
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
+ }
81909
83059
  return lines.join("\n");
81910
83060
  }
81911
83061
  async function repairAgentIntegrations(options2 = {}) {
@@ -81919,6 +83069,10 @@ async function repairAgentIntegrations(options2 = {}) {
81919
83069
  if (isMcpConfigAgent(entry.agent)) {
81920
83070
  for (const check2 of entry.mcp.checks) {
81921
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
+ }
81922
83076
  if (check2.status === "missing" && !canInstallMissing) {
81923
83077
  skipped.push(`${entry.agent}:mcp:${check2.scope}:missing`);
81924
83078
  continue;
@@ -84986,32 +86140,32 @@ __export(cost_tracker_exports, {
84986
86140
  formatCostSummary: () => formatCostSummary,
84987
86141
  isBudgetExceeded: () => isBudgetExceeded
84988
86142
  });
84989
- function calculateModelCost(model, usage2, customPrices) {
86143
+ function calculateModelCost(model, usage3, customPrices) {
84990
86144
  const prices = { ...DEFAULT_PRICES, ...customPrices };
84991
86145
  const price = findPrice(model, prices);
84992
86146
  if (!price) return null;
84993
- const inputCost = usage2.inputTokens / 1e6 * price.inputPer1M;
84994
- const outputCost = usage2.outputTokens / 1e6 * price.outputPer1M;
84995
- const cacheReadCost = usage2.cacheReadTokens / 1e6 * (price.cacheReadPer1M ?? 0);
84996
- const cacheWriteCost = usage2.cacheWriteTokens / 1e6 * (price.cacheWritePer1M ?? 0);
86147
+ const inputCost = usage3.inputTokens / 1e6 * price.inputPer1M;
86148
+ const outputCost = usage3.outputTokens / 1e6 * price.outputPer1M;
86149
+ const cacheReadCost = usage3.cacheReadTokens / 1e6 * (price.cacheReadPer1M ?? 0);
86150
+ const cacheWriteCost = usage3.cacheWriteTokens / 1e6 * (price.cacheWritePer1M ?? 0);
84997
86151
  return inputCost + outputCost + cacheReadCost + cacheWriteCost;
84998
86152
  }
84999
86153
  function calculatePipelineCost(tokenUsage, budgetUSD, customPrices) {
85000
86154
  const models2 = [];
85001
86155
  let totalUSD = 0;
85002
86156
  let hasAnyPrice = false;
85003
- for (const [model, usage2] of Object.entries(tokenUsage)) {
85004
- const cost = calculateModelCost(model, usage2, customPrices);
86157
+ for (const [model, usage3] of Object.entries(tokenUsage)) {
86158
+ const cost = calculateModelCost(model, usage3, customPrices);
85005
86159
  if (cost !== null) {
85006
86160
  hasAnyPrice = true;
85007
86161
  totalUSD = (totalUSD ?? 0) + cost;
85008
86162
  }
85009
86163
  models2.push({
85010
86164
  model,
85011
- inputTokens: usage2.inputTokens,
85012
- outputTokens: usage2.outputTokens,
85013
- cacheReadTokens: usage2.cacheReadTokens,
85014
- cacheWriteTokens: usage2.cacheWriteTokens,
86165
+ inputTokens: usage3.inputTokens,
86166
+ outputTokens: usage3.outputTokens,
86167
+ cacheReadTokens: usage3.cacheReadTokens,
86168
+ cacheWriteTokens: usage3.cacheWriteTokens,
85015
86169
  costUSD: cost !== null ? Math.round(cost * 1e4) / 1e4 : null
85016
86170
  // Round to 4 decimals
85017
86171
  });
@@ -85310,7 +86464,7 @@ var init_permission = __esm({
85310
86464
  });
85311
86465
 
85312
86466
  // src/orchestrate/memorix-bridge.ts
85313
- import { createHash as createHash16 } from "crypto";
86467
+ import { createHash as createHash17 } from "crypto";
85314
86468
  async function getStoreObservation() {
85315
86469
  if (!_storeObservation) {
85316
86470
  const mod = await Promise.resolve().then(() => (init_observations(), observations_exports));
@@ -85326,7 +86480,7 @@ async function getSearchObservations() {
85326
86480
  return _searchObservations;
85327
86481
  }
85328
86482
  function automaticActorId(...parts) {
85329
- return `orchestrator:${createHash16("sha256").update(parts.join("\0")).digest("hex").slice(0, 24)}`;
86483
+ return `orchestrator:${createHash17("sha256").update(parts.join("\0")).digest("hex").slice(0, 24)}`;
85330
86484
  }
85331
86485
  function isEligibleAutomaticLesson(entry) {
85332
86486
  return entry.admissionState !== "candidate" && entry.admissionState !== "ephemeral";
@@ -85473,7 +86627,7 @@ function sanitizeErrorPattern(text) {
85473
86627
  }
85474
86628
  function hashErrorPattern(errorOutput) {
85475
86629
  const sanitized = sanitizeErrorPattern(errorOutput).slice(0, 500);
85476
- return createHash16("sha256").update(sanitized).digest("hex").slice(0, 12);
86630
+ return createHash17("sha256").update(sanitized).digest("hex").slice(0, 12);
85477
86631
  }
85478
86632
  function sleep2(ms) {
85479
86633
  return new Promise((resolve6) => setTimeout(() => resolve6([]), ms));
@@ -85934,12 +87088,12 @@ ${formatCostSummary(costSummary)}`);
85934
87088
  activeDispatches.splice(settled.idx, 1);
85935
87089
  const { dispatch, result } = settled;
85936
87090
  if (result.tokenUsage) {
85937
- for (const [model, usage2] of Object.entries(result.tokenUsage)) {
87091
+ for (const [model, usage3] of Object.entries(result.tokenUsage)) {
85938
87092
  const prev = pipelineUsage[model] ?? { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, model };
85939
- prev.inputTokens += usage2.inputTokens;
85940
- prev.outputTokens += usage2.outputTokens;
85941
- prev.cacheReadTokens += usage2.cacheReadTokens;
85942
- prev.cacheWriteTokens += usage2.cacheWriteTokens;
87093
+ prev.inputTokens += usage3.inputTokens;
87094
+ prev.outputTokens += usage3.outputTokens;
87095
+ prev.cacheReadTokens += usage3.cacheReadTokens;
87096
+ prev.cacheWriteTokens += usage3.cacheWriteTokens;
85943
87097
  pipelineUsage[model] = prev;
85944
87098
  }
85945
87099
  if (traceDb && pipelineId) {
@@ -86885,10 +88039,10 @@ ${"\u2550".repeat(60)}`);
86885
88039
  if (result.tokenUsage) {
86886
88040
  console.error(`
86887
88041
  [COST] Token Usage:`);
86888
- for (const [model, usage2] of Object.entries(result.tokenUsage)) {
86889
- const total = usage2.inputTokens + usage2.outputTokens;
86890
- const cacheHits = usage2.cacheReadTokens;
86891
- console.error(` ${model}: ${total.toLocaleString()} tokens (in: ${usage2.inputTokens.toLocaleString()}, out: ${usage2.outputTokens.toLocaleString()}${cacheHits > 0 ? `, cache: ${cacheHits.toLocaleString()}` : ""})`);
88042
+ for (const [model, usage3] of Object.entries(result.tokenUsage)) {
88043
+ const total = usage3.inputTokens + usage3.outputTokens;
88044
+ const cacheHits = usage3.cacheReadTokens;
88045
+ console.error(` ${model}: ${total.toLocaleString()} tokens (in: ${usage3.inputTokens.toLocaleString()}, out: ${usage3.outputTokens.toLocaleString()}${cacheHits > 0 ? `, cache: ${cacheHits.toLocaleString()}` : ""})`);
86892
88046
  }
86893
88047
  }
86894
88048
  if (result.costSummary) {
@@ -91394,6 +92548,16 @@ var GUIDES = {
91394
92548
  'memorix codegraph context-pack --task "trace the auth flow" [--limit 20]'
91395
92549
  ]
91396
92550
  },
92551
+ checkpoint: {
92552
+ summary: "Inspect bounded recovery checkpoints created around host-native context compaction.",
92553
+ usage: [
92554
+ "memorix checkpoint list [--session <id>] [--agent <name>]",
92555
+ "memorix checkpoint show --id <checkpoint-id>",
92556
+ 'memorix checkpoint context [--id <checkpoint-id>] [--task "continue auth fix"]',
92557
+ "memorix checkpoint archive --id <checkpoint-id>"
92558
+ ],
92559
+ notes: ["Checkpoints are host lifecycle evidence, not durable project memory and not transcript backups."]
92560
+ },
91397
92561
  knowledge: {
91398
92562
  summary: "Manage the reviewed, source-backed Knowledge Workspace and project workflows.",
91399
92563
  usage: [
@@ -91709,6 +92873,7 @@ var main = defineCommand({
91709
92873
  resume: () => Promise.resolve().then(() => (init_resume(), resume_exports)).then((m4) => m4.default),
91710
92874
  explain: () => Promise.resolve().then(() => (init_explain(), explain_exports)).then((m4) => m4.default),
91711
92875
  codegraph: () => Promise.resolve().then(() => (init_codegraph(), codegraph_exports)).then((m4) => m4.default),
92876
+ checkpoint: () => Promise.resolve().then(() => (init_checkpoint(), checkpoint_exports)).then((m4) => m4.default),
91712
92877
  knowledge: () => Promise.resolve().then(() => (init_knowledge(), knowledge_exports)).then((m4) => m4.default),
91713
92878
  reasoning: () => Promise.resolve().then(() => (init_reasoning(), reasoning_exports)).then((m4) => m4.default),
91714
92879
  retention: () => Promise.resolve().then(() => (init_retention2(), retention_exports2)).then((m4) => m4.default),
@@ -91790,6 +92955,7 @@ var main = defineCommand({
91790
92955
  "resume",
91791
92956
  "explain",
91792
92957
  "codegraph",
92958
+ "checkpoint",
91793
92959
  "knowledge",
91794
92960
  "reasoning",
91795
92961
  "retention",
@@ -91858,6 +93024,7 @@ var main = defineCommand({
91858
93024
  console.error(" resume Resume prior work with one bounded project brief");
91859
93025
  console.error(" explain Explain where Memorix project context comes from");
91860
93026
  console.error(" codegraph Refresh/status/context-pack for CodeGraph Memory");
93027
+ console.error(" checkpoint Inspect native compact continuity checkpoints");
91861
93028
  console.error(" knowledge Review source-backed knowledge pages and project workflows");
91862
93029
  console.error(" reasoning Store/search decision rationale");
91863
93030
  console.error(" retention Inspect stale/archive status");