@shadowforge0/aquifer-memory 1.6.0 → 1.8.0

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 (44) hide show
  1. package/.env.example +8 -0
  2. package/README.md +72 -0
  3. package/README_CN.md +17 -0
  4. package/README_TW.md +4 -0
  5. package/aquifer.config.example.json +19 -0
  6. package/consumers/cli.js +259 -12
  7. package/consumers/codex-active-checkpoint.js +186 -0
  8. package/consumers/codex-current-memory.js +106 -0
  9. package/consumers/codex-handoff.js +551 -6
  10. package/consumers/codex.js +209 -25
  11. package/consumers/mcp.js +144 -6
  12. package/consumers/shared/config.js +60 -1
  13. package/consumers/shared/factory.js +10 -3
  14. package/core/aquifer.js +357 -838
  15. package/core/backends/capabilities.js +89 -0
  16. package/core/backends/local.js +430 -0
  17. package/core/legacy-bootstrap.js +140 -0
  18. package/core/mcp-manifest.js +66 -2
  19. package/core/memory-bootstrap.js +20 -8
  20. package/core/memory-consolidation.js +365 -11
  21. package/core/memory-promotion.js +157 -26
  22. package/core/memory-recall.js +341 -22
  23. package/core/memory-records.js +347 -11
  24. package/core/memory-serving.js +132 -0
  25. package/core/postgres-migrations.js +533 -0
  26. package/core/public-session-filter.js +40 -0
  27. package/core/recall-runtime.js +115 -0
  28. package/core/scope-attribution.js +279 -0
  29. package/core/session-checkpoint-producer.js +412 -0
  30. package/core/session-checkpoints.js +432 -0
  31. package/core/session-finalization.js +98 -2
  32. package/core/storage-checkpoints.js +546 -0
  33. package/core/storage.js +121 -8
  34. package/docs/getting-started.md +6 -0
  35. package/docs/setup.md +66 -3
  36. package/package.json +8 -4
  37. package/schema/014-v1-checkpoint-runs.sql +349 -0
  38. package/schema/015-v1-evidence-items.sql +92 -0
  39. package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
  40. package/schema/017-v1-memory-record-embeddings.sql +25 -0
  41. package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
  42. package/scripts/codex-checkpoint-commands.js +464 -0
  43. package/scripts/codex-checkpoint-runtime.js +520 -0
  44. package/scripts/codex-recovery.js +246 -1
@@ -7,6 +7,36 @@ const path = require('path');
7
7
 
8
8
  const { createAquiferFromConfig } = require('../consumers/shared/factory');
9
9
  const codex = require('../consumers/codex');
10
+ const {
11
+ cmdCheckpointHeartbeat,
12
+ cmdCheckpointHeartbeatHook,
13
+ cmdCheckpointPrompt,
14
+ cmdCheckpointTick,
15
+ } = require('./codex-checkpoint-commands');
16
+ const {
17
+ acquireHeartbeatClaim,
18
+ checkpointCheckIntervalMs,
19
+ checkpointClaimDir,
20
+ checkpointClaimTtlMs,
21
+ checkpointDueFromMarker,
22
+ checkpointEveryMessages,
23
+ checkpointEveryUserMessages,
24
+ checkpointHeartbeatCommand,
25
+ checkpointMarkerDir,
26
+ checkpointQuietMs,
27
+ checkpointSchedulerDir,
28
+ checkpointSpoolDir,
29
+ defaultHooksPath,
30
+ findNewestJsonlFile,
31
+ inspectCheckpointHeartbeatHook,
32
+ loadRuntimeConfig,
33
+ mergeCheckpointHeartbeatHook,
34
+ readCheckpointMarker,
35
+ readSchedulerMarker,
36
+ releaseHeartbeatClaim,
37
+ writeCheckpointMarker,
38
+ writeSchedulerMarker,
39
+ } = require('./codex-checkpoint-runtime');
10
40
  const DB_ENV_KEYS = new Set(['DATABASE_URL', 'AQUIFER_DB_URL', 'AQUIFER_SCHEMA', 'AQUIFER_TENANT_ID']);
11
41
 
12
42
  const VALUE_FLAGS = new Set([
@@ -16,7 +46,23 @@ const VALUE_FLAGS = new Set([
16
46
  'except-session-id',
17
47
  'file-path',
18
48
  'finalizer-model',
49
+ 'checkpoint-every-messages',
50
+ 'checkpoint-every-user-messages',
51
+ 'checkpoint-check-interval-ms',
52
+ 'checkpoint-check-interval-minutes',
53
+ 'checkpoint-claim-ttl-ms',
54
+ 'checkpoint-claim-dir',
55
+ 'checkpoint-marker-dir',
56
+ 'checkpoint-scheduler-dir',
57
+ 'checkpoint-spool-dir',
58
+ 'checkpoint-quiet-ms',
59
+ 'hook-event-name',
60
+ 'hooks-path',
19
61
  'idle-ms',
62
+ 'max-checkpoint-bytes',
63
+ 'max-checkpoint-chars',
64
+ 'max-checkpoint-messages',
65
+ 'max-checkpoint-prompt-tokens',
20
66
  'max-candidates',
21
67
  'max-recovery-bytes',
22
68
  'max-recovery-chars',
@@ -27,6 +73,8 @@ const VALUE_FLAGS = new Set([
27
73
  'reason',
28
74
  'scope-kind',
29
75
  'scope-key',
76
+ 'active-scope-key',
77
+ 'active-scope-path',
30
78
  'session-id',
31
79
  'session-key',
32
80
  'sessions-dir',
@@ -36,6 +84,11 @@ const VALUE_FLAGS = new Set([
36
84
  'summary-json',
37
85
  'summary-text',
38
86
  'verdict',
87
+ 'workspace',
88
+ 'workspace-path',
89
+ 'project',
90
+ 'project-key',
91
+ 'repo-path',
39
92
  ]);
40
93
 
41
94
  function parseArgs(argv) {
@@ -109,7 +162,11 @@ function buildRecoveryOptions(flags = {}, env = process.env) {
109
162
  agentId: flags['agent-id'] || envDefault(env, 'CODEX_AQUIFER_AGENT_ID', 'AQUIFER_AGENT_ID') || 'main',
110
163
  source: flags.source || envDefault(env, 'CODEX_AQUIFER_SOURCE', 'AQUIFER_SOURCE') || 'codex',
111
164
  sessionKey: flags['session-key'] || envDefault(env, 'CODEX_AQUIFER_SESSION_KEY') || 'codex:cli',
165
+ workspace: flags.workspace || flags['workspace-path'] || envDefault(env, 'CODEX_AQUIFER_WORKSPACE', 'CODEX_WORKSPACE') || undefined,
166
+ project: flags.project || flags['project-key'] || envDefault(env, 'CODEX_AQUIFER_PROJECT', 'CODEX_PROJECT') || undefined,
167
+ repoPath: flags['repo-path'] || envDefault(env, 'CODEX_AQUIFER_REPO_PATH', 'CODEX_REPO_PATH') || undefined,
112
168
  codexHome: flags['codex-home'] || envDefault(env, 'CODEX_HOME') || undefined,
169
+ hooksPath: flags['hooks-path'] || undefined,
113
170
  stateDir: flags['state-dir'] || undefined,
114
171
  sessionsDir: flags['sessions-dir'] || undefined,
115
172
  maxRecoveryCandidates: parseIntFlag(flags['max-candidates'], 1),
@@ -122,6 +179,7 @@ function buildRecoveryOptions(flags = {}, env = process.env) {
122
179
  includeJsonlPreviews: flags['include-jsonl-previews'] === true,
123
180
  includeDeferredRecovery: flags['include-deferred'] === true,
124
181
  excludeNewest: flags['include-current'] === true ? false : true,
182
+ strictWrapperEnv: flags['strict-wrapper-env'] === true,
125
183
  };
126
184
  for (const [key, value] of Object.entries(opts)) {
127
185
  if (value === undefined) delete opts[key];
@@ -129,6 +187,10 @@ function buildRecoveryOptions(flags = {}, env = process.env) {
129
187
  return opts;
130
188
  }
131
189
 
190
+ function addDoctorCheck(checks, name, status, detail, extra = {}) {
191
+ checks.push({ name, status, detail, ...extra });
192
+ }
193
+
132
194
  function shellQuote(value) {
133
195
  return `'${String(value || '').replace(/'/g, `'\\''`)}'`;
134
196
  }
@@ -266,6 +328,91 @@ function compactCandidate(candidate = {}) {
266
328
  };
267
329
  }
268
330
 
331
+ function compactDoctorOptions(opts = {}) {
332
+ return {
333
+ agentId: opts.agentId || 'main',
334
+ source: opts.source || 'codex',
335
+ sessionKey: opts.sessionKey || 'codex:cli',
336
+ workspace: opts.workspace || null,
337
+ project: opts.project || null,
338
+ repoPath: opts.repoPath || null,
339
+ codexHome: opts.codexHome || null,
340
+ hooksPath: opts.hooksPath || null,
341
+ sessionsDir: opts.sessionsDir || null,
342
+ stateDir: opts.stateDir || null,
343
+ excludeNewest: opts.excludeNewest !== false,
344
+ includeDeferredRecovery: opts.includeDeferredRecovery === true,
345
+ maxRecoveryCandidates: opts.maxRecoveryCandidates || null,
346
+ };
347
+ }
348
+
349
+ async function buildDoctorReport(aquifer, opts = {}, env = process.env) {
350
+ const checks = [];
351
+ const hasWrapperEnv = Boolean(
352
+ env.CODEX_AQUIFER_AGENT_ID
353
+ || env.CODEX_AQUIFER_SOURCE
354
+ || env.CODEX_AQUIFER_SESSION_KEY
355
+ || env.CODEX_HOME
356
+ || env.CODEX_ENV_PATH,
357
+ );
358
+ if (hasWrapperEnv) {
359
+ addDoctorCheck(checks, 'wrapper_env', 'ok', 'Codex wrapper env is present.');
360
+ } else if (opts.strictWrapperEnv) {
361
+ addDoctorCheck(checks, 'wrapper_env', 'fail', 'Strict wrapper env requested, but no CODEX_AQUIFER_* or CODEX_HOME env was found.');
362
+ } else {
363
+ addDoctorCheck(checks, 'wrapper_env', 'warn', 'Using CLI defaults; pass --strict-wrapper-env for live wrapper deployment checks.');
364
+ }
365
+
366
+ if (opts.excludeNewest === false) {
367
+ addDoctorCheck(checks, 'current_transcript_guard', 'fail', 'Current/newest transcript exclusion is disabled.');
368
+ } else {
369
+ addDoctorCheck(checks, 'current_transcript_guard', 'ok', 'Newest transcript exclusion is enabled.');
370
+ }
371
+
372
+ const heartbeatHook = inspectCheckpointHeartbeatHook(opts);
373
+ addDoctorCheck(
374
+ checks,
375
+ 'checkpoint_heartbeat_hook',
376
+ heartbeatHook.status,
377
+ heartbeatHook.detail,
378
+ { hooksPath: heartbeatHook.hooksPath, installed: heartbeatHook.installed },
379
+ );
380
+
381
+ let candidates = [];
382
+ try {
383
+ candidates = await listDbEligibleCandidates(aquifer, {
384
+ ...opts,
385
+ idleMs: opts.idleMs ?? 0,
386
+ includeJsonlPreviews: true,
387
+ maxRecoveryCandidates: opts.maxRecoveryCandidates || 1,
388
+ });
389
+ addDoctorCheck(
390
+ checks,
391
+ 'sessionstart_preflight',
392
+ 'ok',
393
+ `Metadata-only recovery scan completed; eligibleCandidates=${candidates.length}.`,
394
+ { eligibleCandidates: candidates.length },
395
+ );
396
+ } catch (err) {
397
+ addDoctorCheck(
398
+ checks,
399
+ 'sessionstart_preflight',
400
+ 'fail',
401
+ err && err.message ? err.message : String(err),
402
+ );
403
+ }
404
+
405
+ const status = checks.some(check => check.status === 'fail')
406
+ ? 'fail'
407
+ : checks.some(check => check.status === 'warn') ? 'warn' : 'ok';
408
+ return {
409
+ status,
410
+ checks,
411
+ options: compactDoctorOptions(opts),
412
+ candidates: candidates.map(compactCandidate),
413
+ };
414
+ }
415
+
269
416
  function parseIdList(value) {
270
417
  if (!value || value === true) return new Set();
271
418
  return new Set(String(value).split(',').map(part => part.trim()).filter(Boolean));
@@ -467,6 +614,42 @@ async function cmdDecision(aquifer, flags, opts) {
467
614
  console.log(`Recovery ${verdict}: ${candidate.sessionId}`);
468
615
  }
469
616
 
617
+ async function cmdDoctor(aquifer, flags, opts, env = process.env) {
618
+ const report = await buildDoctorReport(aquifer, opts, env);
619
+ printDoctorReport(report, flags);
620
+ return report;
621
+ }
622
+
623
+ function printDoctorReport(report = {}, flags = {}) {
624
+ if (flags.json) {
625
+ console.log(JSON.stringify(report, null, 2));
626
+ } else {
627
+ console.log(`Codex recovery doctor: ${report.status}`);
628
+ for (const check of report.checks || []) {
629
+ console.log(`- ${check.status} ${check.name}: ${check.detail}`);
630
+ }
631
+ }
632
+ if (report.status === 'fail') process.exitCode = 1;
633
+ }
634
+
635
+ async function cmdDoctorInitFailure(flags, opts, err, env = process.env) {
636
+ let report = await buildDoctorReport(null, opts, env);
637
+ report = {
638
+ ...report,
639
+ status: 'fail',
640
+ checks: [
641
+ {
642
+ name: 'aquifer_init',
643
+ status: 'fail',
644
+ detail: err && err.message ? err.message : String(err),
645
+ },
646
+ ...(report.checks || []),
647
+ ],
648
+ };
649
+ printDoctorReport(report, flags);
650
+ return report;
651
+ }
652
+
470
653
  async function main(argv = process.argv.slice(2)) {
471
654
  const args = parseArgs(argv);
472
655
  const command = args._[0] || 'help';
@@ -478,9 +661,35 @@ async function main(argv = process.argv.slice(2)) {
478
661
  node scripts/codex-recovery.js hook-context [options]
479
662
  node scripts/codex-recovery.js preview [options]
480
663
  node scripts/codex-recovery.js prompt --session-id ID [options]
664
+ node scripts/codex-recovery.js checkpoint-prompt --file-path FILE --scope-key KEY [options]
665
+ node scripts/codex-recovery.js checkpoint-tick --scope-key KEY [--file-path FILE|--sessions-dir DIR] [options]
666
+ node scripts/codex-recovery.js checkpoint-heartbeat --hook-stdin --scope-key KEY [options]
667
+ node scripts/codex-recovery.js checkpoint-heartbeat-hook --scope-key KEY [--hooks-path FILE] [--apply]
481
668
  node scripts/codex-recovery.js finalize --session-id ID --summary-stdin [options]
482
669
  node scripts/codex-recovery.js decision --session-id ID --verdict declined|deferred [options]
483
- node scripts/codex-recovery.js decision --all --verdict declined|deferred [options]`);
670
+ node scripts/codex-recovery.js decision --all --verdict declined|deferred [options]
671
+ node scripts/codex-recovery.js doctor [--strict-wrapper-env] [--json]`);
672
+ return;
673
+ }
674
+
675
+ if (command === 'doctor') {
676
+ try {
677
+ await withAquifer(async (aquifer) => {
678
+ await cmdDoctor(aquifer, args.flags, opts);
679
+ });
680
+ } catch (err) {
681
+ await cmdDoctorInitFailure(args.flags, opts, err);
682
+ }
683
+ return;
684
+ }
685
+
686
+ if (command === 'checkpoint-heartbeat') {
687
+ await cmdCheckpointHeartbeat(null, args.flags, opts);
688
+ return;
689
+ }
690
+
691
+ if (command === 'checkpoint-heartbeat-hook') {
692
+ await cmdCheckpointHeartbeatHook(args.flags, opts);
484
693
  return;
485
694
  }
486
695
 
@@ -495,6 +704,12 @@ async function main(argv = process.argv.slice(2)) {
495
704
  case 'prompt':
496
705
  await cmdPrompt(aquifer, args.flags, opts);
497
706
  break;
707
+ case 'checkpoint-prompt':
708
+ await cmdCheckpointPrompt(aquifer, args.flags, opts);
709
+ break;
710
+ case 'checkpoint-tick':
711
+ await cmdCheckpointTick(aquifer, args.flags, opts);
712
+ break;
498
713
  case 'finalize':
499
714
  await cmdFinalize(aquifer, args.flags, opts);
500
715
  break;
@@ -508,16 +723,46 @@ async function main(argv = process.argv.slice(2)) {
508
723
  }
509
724
 
510
725
  module.exports = {
726
+ buildDoctorReport,
511
727
  buildRecoveryOptions,
512
728
  cmdDecision,
729
+ cmdDoctor,
730
+ cmdDoctorInitFailure,
513
731
  cmdFinalize,
514
732
  cmdHookContext,
733
+ cmdCheckpointHeartbeat,
734
+ cmdCheckpointHeartbeatHook,
735
+ cmdCheckpointPrompt,
736
+ cmdCheckpointTick,
515
737
  cmdPrompt,
738
+ acquireHeartbeatClaim,
739
+ checkpointDueFromMarker,
740
+ checkpointHeartbeatCommand,
741
+ checkpointCheckIntervalMs,
742
+ checkpointEveryMessages,
743
+ checkpointEveryUserMessages,
744
+ checkpointQuietMs,
745
+ checkpointClaimDir,
746
+ checkpointClaimTtlMs,
747
+ checkpointMarkerDir,
748
+ checkpointSchedulerDir,
749
+ checkpointSpoolDir,
750
+ defaultHooksPath,
751
+ findNewestJsonlFile,
752
+ inspectCheckpointHeartbeatHook,
753
+ loadRuntimeConfig,
516
754
  loadCodexEnv,
755
+ main,
756
+ mergeCheckpointHeartbeatHook,
517
757
  parseArgs,
758
+ readCheckpointMarker,
759
+ readSchedulerMarker,
760
+ releaseHeartbeatClaim,
518
761
  renderFinalizeCommand,
519
762
  renderHookContext,
520
763
  selectCandidate,
764
+ writeCheckpointMarker,
765
+ writeSchedulerMarker,
521
766
  };
522
767
 
523
768
  if (require.main === module) {