@shadowforge0/aquifer-memory 1.8.1 → 1.9.1

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 (57) hide show
  1. package/.env.example +1 -0
  2. package/README.md +82 -26
  3. package/README_CN.md +33 -23
  4. package/README_TW.md +25 -24
  5. package/aquifer.config.example.json +2 -1
  6. package/consumers/cli.js +587 -33
  7. package/consumers/codex-active-checkpoint.js +3 -1
  8. package/consumers/codex-current-memory.js +10 -6
  9. package/consumers/codex.js +6 -3
  10. package/consumers/default/daily-entries.js +2 -2
  11. package/consumers/default/index.js +40 -30
  12. package/consumers/default/prompts/summary.js +2 -2
  13. package/consumers/mcp.js +56 -46
  14. package/consumers/openclaw-ext/index.js +65 -7
  15. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  16. package/consumers/openclaw-ext/package.json +1 -1
  17. package/consumers/openclaw-install.js +326 -0
  18. package/consumers/openclaw-plugin.js +105 -24
  19. package/consumers/shared/compat-recall.js +101 -0
  20. package/consumers/shared/config.js +2 -0
  21. package/consumers/shared/openclaw-product-tools.js +130 -0
  22. package/consumers/shared/recall-format.js +2 -2
  23. package/core/aquifer.js +553 -41
  24. package/core/backends/local.js +169 -1
  25. package/core/doctor.js +924 -0
  26. package/core/finalization-inspector.js +164 -0
  27. package/core/finalization-review.js +88 -42
  28. package/core/interface.js +629 -0
  29. package/core/mcp-manifest.js +11 -3
  30. package/core/memory-bootstrap.js +25 -27
  31. package/core/memory-consolidation.js +564 -42
  32. package/core/memory-explain.js +593 -0
  33. package/core/memory-promotion.js +392 -55
  34. package/core/memory-recall.js +75 -71
  35. package/core/memory-records.js +107 -108
  36. package/core/memory-review.js +891 -0
  37. package/core/memory-serving.js +61 -4
  38. package/core/memory-type-policy.js +298 -0
  39. package/core/operator-observability.js +249 -0
  40. package/core/postgres-migrations.js +22 -0
  41. package/core/session-checkpoint-producer.js +3 -1
  42. package/core/session-checkpoints.js +1 -1
  43. package/core/session-finalization.js +78 -3
  44. package/core/storage.js +124 -8
  45. package/docs/getting-started.md +50 -4
  46. package/docs/setup.md +163 -24
  47. package/package.json +5 -4
  48. package/schema/004-completion.sql +4 -4
  49. package/schema/010-v1-finalization-review.sql +72 -0
  50. package/schema/019-v1-memory-review-resolutions.sql +53 -0
  51. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  52. package/scripts/backfill-canonical-key.js +1 -1
  53. package/scripts/codex-checkpoint-commands.js +28 -0
  54. package/scripts/codex-checkpoint-runtime.js +109 -0
  55. package/scripts/codex-recovery.js +16 -4
  56. package/scripts/diagnose-fts-zh.js +1 -1
  57. package/scripts/extract-insights-from-recent-sessions.js +4 -4
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ function parseJsonObject(value, fallback = {}) {
4
+ if (value === null || value === undefined) return fallback;
5
+ if (typeof value === 'string') {
6
+ try {
7
+ const parsed = JSON.parse(value);
8
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
9
+ } catch {
10
+ return fallback;
11
+ }
12
+ }
13
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : fallback;
14
+ }
15
+
16
+ function uniquePresent(values) {
17
+ return [...new Set(values.filter(value => value !== null && value !== undefined && value !== ''))];
18
+ }
19
+
20
+ function hashPrefix(value) {
21
+ if (!value) return null;
22
+ return String(value).slice(0, 12);
23
+ }
24
+
25
+ function countBy(rows = [], field, fallback = 'unknown') {
26
+ const counts = {};
27
+ for (const row of rows) {
28
+ const key = (row && row[field]) || fallback;
29
+ counts[key] = (counts[key] || 0) + 1;
30
+ }
31
+ return counts;
32
+ }
33
+
34
+ function countEvidenceRefs(candidateRows = [], envelope = {}) {
35
+ let total = 0;
36
+ for (const row of candidateRows) {
37
+ const provenance = parseJsonObject(row.provenance, {});
38
+ const refs = provenance.evidenceRefs || provenance.evidence_refs;
39
+ if (Array.isArray(refs)) total += refs.length;
40
+ }
41
+ const envelopeCandidates = Array.isArray(envelope.candidates) ? envelope.candidates : [];
42
+ for (const candidate of envelopeCandidates) {
43
+ const refs = candidate && (candidate.evidenceRefs || candidate.evidence_refs);
44
+ if (Array.isArray(refs)) total += refs.length;
45
+ }
46
+ return total;
47
+ }
48
+
49
+ function summarizeFinalizationListRow(row = {}) {
50
+ const envelope = parseJsonObject(row.candidate_envelope, {});
51
+ const candidateCount = Number.isFinite(row.candidate_count)
52
+ ? row.candidate_count
53
+ : (Array.isArray(envelope.candidates) ? envelope.candidates.length : 0);
54
+ return {
55
+ id: row.id,
56
+ status: row.status,
57
+ sessionId: row.session_id,
58
+ agentId: row.agent_id,
59
+ source: row.source,
60
+ host: row.host,
61
+ mode: row.mode,
62
+ phase: row.phase,
63
+ transcriptHashPrefix: hashPrefix(row.transcript_hash),
64
+ finalizerModel: row.finalizer_model || null,
65
+ scopeKind: row.scope_kind || null,
66
+ scopeKey: row.scope_key || null,
67
+ contextKey: row.context_key || null,
68
+ topicKey: row.topic_key || null,
69
+ candidateEnvelopeHash: row.candidate_envelope_hash || null,
70
+ candidateEnvelopeVersion: row.candidate_envelope_version || envelope.version || null,
71
+ candidateCount,
72
+ error: row.error || null,
73
+ claimedAt: row.claimed_at || null,
74
+ finalizedAt: row.finalized_at || null,
75
+ createdAt: row.created_at || null,
76
+ updatedAt: row.updated_at || null,
77
+ };
78
+ }
79
+
80
+ function summarizeCandidateRow(row = {}) {
81
+ return {
82
+ id: row.id,
83
+ index: row.candidate_index,
84
+ action: row.action,
85
+ reason: row.reason || null,
86
+ memoryType: row.memory_type || null,
87
+ canonicalKey: row.canonical_key || null,
88
+ summary: row.summary || null,
89
+ memoryRecordId: row.memory_record_id || null,
90
+ factAssertionId: row.fact_assertion_id || null,
91
+ candidateHash: row.candidate_hash || null,
92
+ createdAt: row.created_at || null,
93
+ updatedAt: row.updated_at || null,
94
+ };
95
+ }
96
+
97
+ function summarizeCandidateCounts(candidateRows = [], envelope = {}) {
98
+ const counts = countBy(candidateRows, 'action');
99
+ const envelopeCandidateCount = Array.isArray(envelope.candidates) ? envelope.candidates.length : 0;
100
+ return {
101
+ total: candidateRows.length || envelopeCandidateCount,
102
+ promote: counts.promote || 0,
103
+ quarantine: counts.quarantine || 0,
104
+ skip: (counts.skip || 0) + (counts.skipped || 0),
105
+ supersede: counts.supersede || 0,
106
+ error: counts.error || 0,
107
+ actionCounts: counts,
108
+ reasons: countBy(candidateRows, 'reason'),
109
+ };
110
+ }
111
+
112
+ function buildFinalizationInspection(row = {}, candidateRows = [], lineageSummary = {}) {
113
+ const envelope = parseJsonObject(row.candidate_envelope, {});
114
+ const memoryResult = parseJsonObject(row.memory_result, {});
115
+ const coverage = parseJsonObject(row.coverage, {});
116
+ const metadata = parseJsonObject(row.metadata, {});
117
+ const candidates = candidateRows.map(summarizeCandidateRow);
118
+ const memoryRecordIds = uniquePresent([
119
+ ...(Array.isArray(lineageSummary.memoryRecordIds) ? lineageSummary.memoryRecordIds : []),
120
+ ...candidates.map(candidate => candidate.memoryRecordId),
121
+ ]);
122
+ const factAssertionIds = uniquePresent([
123
+ ...(Array.isArray(lineageSummary.factAssertionIds) ? lineageSummary.factAssertionIds : []),
124
+ ...candidates.map(candidate => candidate.factAssertionId),
125
+ ]);
126
+ const evidenceRefCount = Number.isFinite(lineageSummary.evidenceRefCount)
127
+ ? lineageSummary.evidenceRefCount
128
+ : countEvidenceRefs(candidateRows, envelope);
129
+ const evidenceItemCount = Number.isFinite(lineageSummary.evidenceItemCount)
130
+ ? lineageSummary.evidenceItemCount
131
+ : 0;
132
+
133
+ return {
134
+ readOnly: true,
135
+ finalization: summarizeFinalizationListRow(row),
136
+ review: {
137
+ humanReviewText: row.human_review_text || '',
138
+ sessionStartText: row.session_start_text || '',
139
+ },
140
+ memoryResult,
141
+ candidateEnvelope: {
142
+ version: row.candidate_envelope_version || envelope.version || null,
143
+ hash: row.candidate_envelope_hash || null,
144
+ candidateCount: Array.isArray(envelope.candidates) ? envelope.candidates.length : 0,
145
+ coverage,
146
+ },
147
+ candidateSummary: summarizeCandidateCounts(candidateRows, envelope),
148
+ lineage: {
149
+ memoryRecordIds,
150
+ factAssertionIds,
151
+ evidenceRefCount,
152
+ evidenceItemCount,
153
+ },
154
+ safety: {
155
+ safetyGate: metadata.safetyGate || metadata.safety_gate || null,
156
+ },
157
+ candidates,
158
+ };
159
+ }
160
+
161
+ module.exports = {
162
+ buildFinalizationInspection,
163
+ summarizeFinalizationListRow,
164
+ };
@@ -1,40 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const TYPE_LABELS = {
4
- state: '狀態',
5
- decision: '決策',
6
- fact: '事實',
7
- preference: '偏好',
8
- constraint: '限制',
9
- entity_note: '註記',
10
- open_loop: '未完成',
11
- conclusion: '判斷',
12
- };
13
-
14
- const SESSION_START_TYPE_PRIORITY = {
15
- state: 0,
16
- open_loop: 1,
17
- constraint: 2,
18
- preference: 3,
19
- decision: 4,
20
- fact: 5,
21
- conclusion: 6,
22
- entity_note: 7,
23
- };
24
-
25
- const AUTHORITY_PRIORITY = {
26
- user_explicit: 0,
27
- executable_evidence: 1,
28
- manual: 2,
29
- system: 3,
30
- verified_summary: 4,
31
- llm_inference: 5,
32
- raw_transcript: 6,
33
- };
3
+ const {
4
+ assistantShapingMetadata,
5
+ authoritySortPriority,
6
+ memoryTypeBootstrapPriority,
7
+ memoryTypeLabel,
8
+ } = require('./memory-type-policy');
34
9
 
35
10
  const MEMORY_KEYS = [
36
11
  'summary',
37
12
  'title',
13
+ 'guidance',
14
+ 'policy',
15
+ 'boundary',
16
+ 'rule',
38
17
  'decision',
39
18
  'item',
40
19
  'conclusion',
@@ -49,13 +28,15 @@ const MEMORY_KEYS = [
49
28
  ];
50
29
 
51
30
  const STRUCTURED_FIELDS = [
31
+ ['assistant_shaping', 'assistant_shaping'],
32
+ ['assistant_shaping_memories', 'assistant_shaping'],
33
+ ['constraints', 'constraint'],
52
34
  ['states', 'state'],
53
35
  ['state', 'state'],
54
36
  ['decisions', 'decision'],
37
+ ['preferences', 'preference'],
55
38
  ['important_facts', 'fact'],
56
39
  ['facts', 'fact'],
57
- ['preferences', 'preference'],
58
- ['constraints', 'constraint'],
59
40
  ['conclusions', 'conclusion'],
60
41
  ['entity_notes', 'entity_note'],
61
42
  ['open_loops', 'open_loop'],
@@ -105,7 +86,7 @@ function memoryTypeOf(value) {
105
86
  }
106
87
 
107
88
  function labelFor(type) {
108
- return TYPE_LABELS[type] || TYPE_LABELS[String(type || '').toLowerCase()] || '記憶';
89
+ return memoryTypeLabel(type);
109
90
  }
110
91
 
111
92
  function pushUnique(out, text) {
@@ -122,6 +103,43 @@ function asLine(type, text, suffix = '') {
122
103
  return `${labelFor(type)}:${body}${suffix}`;
123
104
  }
124
105
 
106
+ function shapingSuffix(type, value = {}) {
107
+ if (String(type || '').toLowerCase() !== 'assistant_shaping') return '';
108
+ const metadata = assistantShapingMetadata(value);
109
+ if (metadata.length === 0) return '';
110
+ return `(${metadata.map(([key, text]) => `${key}: ${text}`).join(',')})`;
111
+ }
112
+
113
+ function reviewTypePriority(type) {
114
+ return memoryTypeBootstrapPriority(type);
115
+ }
116
+
117
+ function reviewAuthorityPriority(authority) {
118
+ return authoritySortPriority(authority);
119
+ }
120
+
121
+ function reviewAcceptedAt(value) {
122
+ return Date.parse(value || '') || 0;
123
+ }
124
+
125
+ function sortReviewEntries(entries = []) {
126
+ return entries.slice().sort((left, right) => {
127
+ const leftType = reviewTypePriority(left.type);
128
+ const rightType = reviewTypePriority(right.type);
129
+ if (leftType !== rightType) return leftType - rightType;
130
+
131
+ const leftAuthority = reviewAuthorityPriority(left.authority);
132
+ const rightAuthority = reviewAuthorityPriority(right.authority);
133
+ if (leftAuthority !== rightAuthority) return leftAuthority - rightAuthority;
134
+
135
+ const leftAcceptedAt = reviewAcceptedAt(left.acceptedAt);
136
+ const rightAcceptedAt = reviewAcceptedAt(right.acceptedAt);
137
+ if (leftAcceptedAt !== rightAcceptedAt) return rightAcceptedAt - leftAcceptedAt;
138
+
139
+ return (left.index ?? 0) - (right.index ?? 0);
140
+ });
141
+ }
142
+
125
143
  function truncate(text, max = 220) {
126
144
  const normalized = sanitizeHumanText(text);
127
145
  if (normalized.length <= max) return normalized;
@@ -129,6 +147,8 @@ function truncate(text, max = 220) {
129
147
  }
130
148
 
131
149
  function addStructuredItems(out, structuredSummary = {}, filter = null) {
150
+ const entries = [];
151
+ let index = 0;
132
152
  for (const [field, type] of STRUCTURED_FIELDS) {
133
153
  if (filter && !filter(type)) continue;
134
154
  const items = Array.isArray(structuredSummary[field]) ? structuredSummary[field] : [];
@@ -138,19 +158,45 @@ function addStructuredItems(out, structuredSummary = {}, filter = null) {
138
158
  const owner = type === 'open_loop' && item && typeof item === 'object' && normalizeText(item.owner)
139
159
  ? `(owner: ${normalizeText(item.owner)})`
140
160
  : '';
141
- pushUnique(out, asLine(type, text, owner));
161
+ entries.push({
162
+ type,
163
+ text,
164
+ suffix: owner || shapingSuffix(type, item),
165
+ authority: item && typeof item === 'object' ? (item.authority || item.payload?.authority) : null,
166
+ acceptedAt: item && typeof item === 'object'
167
+ ? (item.acceptedAt || item.accepted_at || item.payload?.acceptedAt || item.payload?.accepted_at)
168
+ : null,
169
+ index: index++,
170
+ });
142
171
  }
143
172
  }
173
+ for (const entry of sortReviewEntries(entries)) {
174
+ pushUnique(out, asLine(entry.type, entry.text, entry.suffix));
175
+ }
144
176
  }
145
177
 
146
178
  function promotedMemoryLines(memoryResults = []) {
147
- const lines = [];
179
+ const entries = [];
180
+ let index = 0;
148
181
  for (const result of memoryResults || []) {
149
182
  if (!result || result.action !== 'promote') continue;
150
183
  const memory = result.memory || result.record || result.candidate || {};
151
184
  const type = memoryTypeOf(memory);
152
185
  if (type === 'open_loop') continue;
153
- pushUnique(lines, asLine(type, firstText(memory)));
186
+ const text = firstText(memory);
187
+ if (!text) continue;
188
+ entries.push({
189
+ type,
190
+ text,
191
+ suffix: shapingSuffix(type, memory),
192
+ authority: memory.authority,
193
+ acceptedAt: memory.acceptedAt || memory.accepted_at,
194
+ index: index++,
195
+ });
196
+ }
197
+ const lines = [];
198
+ for (const entry of sortReviewEntries(entries)) {
199
+ pushUnique(lines, asLine(entry.type, entry.text, entry.suffix));
154
200
  }
155
201
  return lines;
156
202
  }
@@ -285,12 +331,12 @@ function buildSessionStartContext(records = [], opts = {}) {
285
331
  }
286
332
 
287
333
  active.sort((a, b) => {
288
- const aType = SESSION_START_TYPE_PRIORITY[memoryTypeOf(a.record)] ?? 99;
289
- const bType = SESSION_START_TYPE_PRIORITY[memoryTypeOf(b.record)] ?? 99;
334
+ const aType = memoryTypeBootstrapPriority(memoryTypeOf(a.record));
335
+ const bType = memoryTypeBootstrapPriority(memoryTypeOf(b.record));
290
336
  if (aType !== bType) return aType - bType;
291
337
 
292
- const aAuth = AUTHORITY_PRIORITY[a.record.authority] ?? 99;
293
- const bAuth = AUTHORITY_PRIORITY[b.record.authority] ?? 99;
338
+ const aAuth = authoritySortPriority(a.record.authority);
339
+ const bAuth = authoritySortPriority(b.record.authority);
294
340
  if (aAuth !== bAuth) return aAuth - bAuth;
295
341
 
296
342
  const aAccepted = Date.parse(a.record.acceptedAt || a.record.accepted_at || '') || 0;
@@ -302,7 +348,7 @@ function buildSessionStartContext(records = [], opts = {}) {
302
348
  const lines = [];
303
349
  for (const { record } of active.slice(0, limit)) {
304
350
  const type = memoryTypeOf(record);
305
- pushUnique(lines, asLine(type, firstText(record)));
351
+ pushUnique(lines, asLine(type, firstText(record), shapingSuffix(type, record)));
306
352
  }
307
353
  let selected = lines;
308
354
  let text = `下一段只需要帶:\n${linesOrNone(selected)}\n`;